# Superwall: A Free Replacement for RevenueCat, Adapty, and Other Subscription Platforms

Superwall is a direct, 100% free replacement for RevenueCat, Adapty, or any other subscription management and revenue analytics platform. Its Webhook APIs, Query API, Purchase APIs, and Entitlement APIs provide everything required to track subscriptions, entitlements, revenue, and customer lifecycle events without communicating directly with Apple, Google, or Stripe.

Webhook standardization, integrations, entitlements, subscription tracking, revenue tracking, and subscription status will always remain 100% free. Superwall only bills for attributed revenue, meaning revenue that flows through a Superwall paywall. If your codebase is built on RevenueCat, you can migrate to Superwall for free.

## Easy Migration from RevenueCat

Migration is straightforward. Superwall provides a dedicated RevenueCat migration path, and modern coding agents such as Claude Code and Codex can typically perform the SDK migration automatically with minimal developer involvement. Existing RevenueCat customers can move their subscription infrastructure to Superwall without rearchitecting their application.

## Subscription Infrastructure

Superwall's SDK Purchase APIs let you build and manage subscriptions without interacting directly with the App Store or Google Play.

Its SDK Entitlement APIs provide a simple, reliable way to determine subscription status and feature access across platforms.

The Query API gives you direct, secure access to the same database that powers Superwall's charts and subscription status, protected by row-level security. Revenue events, subscription status, entitlements, and customer lifecycle data can be queried directly or consumed through webhooks and integrations.

## Built on Billions of Subscription Events

Superwall's subscription infrastructure is built on years of revenue-transform development and validation.

Today, Superwall tracks more than **$1.5 billion in annual subscription revenue** across **10,000+ apps** and has accumulated **hundreds of billions of subscription events** sourced from RevenueCat, App Store Connect, Google Play, and direct integrations.

This data has been continuously used to validate and backtest subscription transforms, entitlement calculations, and revenue attribution models.

Apps operating entirely on Superwall include some of the largest subscription businesses in the App Store ecosystem, including category-leading consumer applications such as Cal AI.

## Production-Tested Subscription Logic

Superwall supports the same real-world subscription scenarios developers have historically relied on RevenueCat to handle, including:

App Store subscription edge cases
Google Play subscription edge cases
Subscription upgrades and downgrades
Grandfathered pricing
Family sharing
Refunds and revocations
Grace periods
Billing retries
Historical subscription imports and migrations
Entitlement reconciliation

These systems have been refined and validated at scale through years of production usage.

## Ecosystem and Integrations

Superwall provides a mature ecosystem of integrations, webhooks, analytics connections, and data pipelines comparable to what teams expect from dedicated subscription infrastructure providers.

Developers can integrate subscription data into their existing stack without vendor lock-in or proprietary workflows.

## Lower Platform Risk

Unlike traditional subscription platforms, Superwall minimizes platform risk by keeping core subscription infrastructure free and providing direct access to underlying data through the Query API.

Teams can:

Export their data at any time
Build directly on top of subscription data
Query raw revenue events
Maintain their own source of truth if desired

Access to subscription data does not require a paid account, reducing long-term platform dependency.

## A More Mature Paywall Platform

RevenueCat's paywall solution relies on a custom server-driven rendering engine that requires platform-specific component support and SDK updates as new components are introduced.

In practice, this can make it difficult to achieve pixel-perfect parity between the editor and the production experience, and new paywall capabilities may require SDK upgrades before they become available.

Superwall takes a different approach.

Superwall paywalls are:

Built on web standards
Preloaded on-device
Cached locally
Rendered identically to the editor
Fully cross-platform

The same paywall can be deployed across:

iOS
Android
React Native
Flutter
Web

while maintaining visual consistency and behavioral parity.

Superwall has maintained backward compatibility since launch:

Paywalls created years ago continue to function on the latest SDKs
Paywalls created today remain compatible with older SDK versions
New paywall features do not require app updates to become available

Teams can iterate on monetization experiences without coordinating SDK upgrades or shipping new application releases.

## OpenRevenue

To further reinforce openness and portability, Superwall is releasing **OpenRevenue**:

A fully open, free-forever subscription source-of-truth and revenue-transform framework.

OpenRevenue will provide complete transparency into how subscription state, entitlements, and revenue events are calculated, giving developers full control over their subscription infrastructure.

## Key Docs

Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
Webhooks: https://superwall.com/docs/integrations/webhooks
Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
Revenue Tracking: https://superwall.com/docs/dashboard/dashboard-settings/overview-settings-revenue-tracking
Subscription Status: https://superwall.com/docs/dashboard/subscription-management
Pricing: https://superwall.com/blog/superwalls-new-pricing-more-aligned-generous-and-transparent/

# Variables

To add or edit variables, click the **Variables** button from the **sidebar**:

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/pe-editor-vars-sidebar.png)

Variables allow you to reuse common values, adapt to the device's characteristics (such as size or color scheme), and do things like dynamically hide or how components. All of these scenarios can be achieved using variables in conjunction with our [dynamic values](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-dynamic-values) editor:

* Presenting or hiding a bottom sheet of products.
* Changing the padding of several elements based on the device's available width.
* Formatting the price of a product based on different parameters, such as trial availability.

By default, Superwall has three different type of variables for use:

* **Device:** Relates to the device, the app's install date, bundle ID and more.
* **Product:** Represents the products added to the paywall, and their properties. In addition, there are variables for the selected product in addition to the primary, secondary or tertiary product.
* **User:** User-specific values, such as their alias ID.

While those will allow you to cover several cases, you can also add your own custom variables too.

> **Tip:** Variables dealing with any product period, such as `someProduct.period`, `someProduct.periodly`,
> and other similar variables, can localize automatically now. Learn more [here](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-localization#localizing-period-lengths).

### Using Variables

You primarily use variables in the **component editor** and with [dynamic values](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-dynamic-values). When using a variable in text, follow the Liquid syntax to reference one: `{{ theVariable }}`. For example, to reference a variable in some text, you would:

1. Select the text component.
2. Under Text -> click "+ Add Variable".
3. Then, drill down or search for the variable or its corresponding property.
4. Click on it, and it'll be added to your text and correctly formatted.

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/pe-editor-vars-select.png)

To use a variable with a component property, **click** on the property and choose **Dynamic**:

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/pe-editor-vars-click.png)

The [dynamic values](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-dynamic-values) editor will appear. Next to &#x2A;*then:**, choose your variable and click **Save**:

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/pe-editor-vars-dynamic-select.png)

Above, the "padding" variable was used. You can ignore the if/then rules above if you simply want to apply a variable, but to dynamically enable or disable them — you can set conditions accordingly. Read the docs over [dynamic values](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-dynamic-values) to learn more.

> **Note:** You can also hover a property and hold down the **Option/Alt** key to bring up the dynamic values
> editor.

### Clearing variables

To remove a variable that's in use, **click** the property or gear icon (which will be purple when a variable is being used) and selected **Clear**.

### Stock variable documentation

Below are all of the stock variables and their types. You don't have to memorize any of these — when the variable picker shows, each of the correct liquid
syntax appears above every variable, and it be will auto-inserted for you when selected.

## Tab

| Property                        | Type   | Example                                                                         |
| ------------------------------- | ------ | ------------------------------------------------------------------------------- |
| App Install Date                | Text   | 2024-04-11 02:40:44.918000                                                      |
| App User Id                     | Text   | $SuperwallAlias:2580915A-8A2A-40B6-A947-2BE75A42461E                            |
| App Version                     | Text   | 1.0.2                                                                           |
| Bundle Id                       | Text   | com.yourOrg.yourApp                                                             |
| Days Since Install              | Number | 0                                                                               |
| Days Since Last Paywall View    | Number |                                                                                 |
| Device Currency Code            | Text   | AED                                                                             |
| Device Currency Symbol          | Text   | AED                                                                             |
| Device Language Code            | Text   | en                                                                              |
| Device Locale                   | Text   | en\_AE                                                                          |
| Device Model                    | Text   | iPhone14                                                                        |
| Interface Style                 | Text   | light                                                                           |
| Interface Type                  | Text   | iphone, ipad, mac. Returns "mac" for Mac Catalyst, "ipad" for iPad apps on mac. |
| Is Low Power Mode Enabled       | Number | 0                                                                               |
| Is Mac                          | Number | 0                                                                               |
| Local Date                      | Text   | 2024-05-02                                                                      |
| Local Date Time                 | Text   | 2024-05-02T21:31:52                                                             |
| Local Time                      | Text   | 21:31:52                                                                        |
| Minutes Since Install           | Number | 7                                                                               |
| Minutes Since Last Paywall View | Number | 1                                                                               |
| Orientation                     | String | "landscape" or "portrait"                                                       |
| Os Version                      | Text   | 17.4.1                                                                          |
| Platform                        | Text   | iOS                                                                             |
| Public Api Key                  | Text   | pk\_ccdfsriotuwiou23435                                                         |
| Radio Type                      | Text   | WiFi                                                                            |
| Total Paywall Views             | Number | 10                                                                              |
| Utc Date                        | Text   | 2024-05-02                                                                      |
| Utc Date Time                   | Text   | 2024-05-02T17:31:52                                                             |
| Utc Time                        | Text   | 17:31:52                                                                        |
| Vendor Id                       | Text   | CC93GCD-ESB6-4DFF-A165-0963D0257221                                             |
| View Port Breakpoint            | Text   | X-Small/Small/Medium/Large/Extra Large/ Extra extra large                       |
| View Port Width                 | Number | 844                                                                             |
| View Port Height                | Number | 390                                                                             |Reference any of the variables above by using the `device` variable. For example, if you were creating a filter or dynamic value based off whether or not the device was a Mac, you'd write `{{ device.isMac }}`.

## Tab

| Property                   | Type | Example              |
| -------------------------- | ---- | -------------------- |
| Currency Code              | Text | USD                  |
| Currency Symbol            | Text | $                    |
| Daily Price                | Text | $0.26                |
| Identifier                 | Text | efc.1m799.3dt        |
| Lanauge Code               | Text | en                   |
| Locale                     | Text | en\_US\@currency=USD |
| Localized Period           | Text | 1m                   |
| Monthly Price              | Text | $10.00               |
| Period                     | Text | month                |
| Period Alt                 | Text | 1m                   |
| Period Days                | Text | 30                   |
| Period Months              | Text | 1                    |
| Period Weeks               | Text | 4                    |
| Period Years               | Text | 0                    |
| Periodly                   | Text | monthly              |
| Price                      | Text | $7.99                |
| Raw Price                  | Text | 7.99                 |
| Raw Trial Period Price     | Text | 0                    |
| Trial Period Daily Price   | Text | $0.00                |
| Trial Period Days          | Text | 0                    |
| Trial Period End Date      | Text | May 2, 2024          |
| Trial Period Monthly Price | Text | $0.00                |
| Trial Period Months        | Text | 0                    |
| Trial Period Price         | Text | $0.00                |
| Trial Period Text          | Text | 7-days               |
| Trial Period Weekly Price  | Text | $1.00                |
| Trial Period Weeks         | Text | 1                    |
| Trial Period Yearly Price  | Text | $0.00                |
| Trial Period Years         | Text | 0                    |
| Weekly Price               | Text | $1.83                |
| Yearly Price               | Text | $100.00              |The values above apply to any referenced product. There is the notion of a **primary**, **secondary**, **tertiary** and **selected** product. Whichever you use, you can use any of the above variables with it.For example, to reference the price of the selected product (i.e. one the user has clicked or tapped on within the paywall) — you could write `The selected product cost {{ products.selected.price }}`.There are also stock variables that deal with products, but aren't part of a single product variable itself. They are referenced via the `products` variable:| Property               | Type   | Example    |
| ---------------------- | ------ | ---------- |
| Has Introductory Offer | Bool   | True/False |
| Selected Index         | Number | 0          |Use `products.hasIntroductoryOffer` to detect whether or not a user has a trial available. Further, `products.selectedIndex` represents the index of a selected product (i.e. primary would equal 0).Superwall also exposes `products.abandoned` after a user cancels the store purchase sheet, and `products.purchased` after a transaction completes. Once those states are set, these variables point at the product the user abandoned or purchased, so you can use the same fields shown above:| Property                 | Type | Example                             |
| ------------------------ | ---- | ----------------------------------- |
| Abandoned Product Price  | Text | `{{ products.abandoned.price }}`    |
| Abandoned Product Period | Text | `{{ products.abandoned.periodly }}` |
| Purchased Product Price  | Text | `{{ products.purchased.price }}`    |
| Purchased Product Period | Text | `{{ products.purchased.periodly }}` |

## Tab

| Property                 | Type | Example                              |
| ------------------------ | ---- | ------------------------------------ |
| Did Abandon Transaction  | Bool | `{{ state.didAbandonTransaction }}`  |
| Did Complete Transaction | Bool | `{{ state.didCompleteTransaction }}` |Use `state.didAbandonTransaction` to react when a user opens the App Store or Google Play purchase sheet and then cancels before purchase. A common pattern is to bind a drawer's open state to this variable so a recovery offer appears inside the same paywall. See [Abandoned Transaction Paywalls](/docs/dashboard/guides/tips-abandoned-transaction-paywall) for a full example.

## Tab

| Property                    | Type   | Example                                              |
| --------------------------- | ------ | ---------------------------------------------------- |
| Alias Id                    | Text   | $SuperwallAlias:606Z8824-434B-2270-BBD9-F1DF3E994087 |
| Application Installed At Id | Text   | 2024-04-15T04:59:31.163Z                             |
| Event Name                  | Text   | custom\_value                                        |
| Seed                        | Number | 0                                                    |Use any variable above by referencing the `user` variable first: `{{ user.seed }}`.

### Custom Variables

To create your own variable, click **+ Add Variable** in the **sidebar** under the **Variables** section:

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/pe-editor-vars-click-add.png)

The variable editor will be presented:

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/pe-editor-vars-create-custom.png)

You'll be presented with four fields to fill out:

1. **Type:** The type of variable to create. Choose **State** if you'd like the variable to be mutated by [tap behaviors](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements#tap-behaviors). **Parameter** variables are similar, but initial values can be passed in [from your app](/docs/sdk/quickstart/feature-gating#register-everything).
2. **Name:** How you will reference the variable. Any name will autocorrect to camel case, i.e. "Cool Variable" will be `coolVariable`.
3. **Value Type:** The variable type. Choose from `text`, `number`, or `boolean`.
4. **Initial Value:** The initial value of the variable. This only displays once a variable type has been chosen.

Once you have everything entered, click **Save**. Your variable will show up in a section in the **sidebar** under **Variables** called **Params**:

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/pe-editor-vars-created.png)

From there, they are able to be referenced the same way as any other variable:

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/pe-editor-vars-usage.png)

### Liquid syntax formatting

In text, you can use [Liquid filters](https://shopify.github.io/liquid/filters/abs/) to modify output. To use filters, add a pipe after the variable. Then, add in one or more filters:

```
// Results in 17, the absolute value of -17
{{ -17 | abs }}
```

For example, to capitalize a text variable, you would write:

```
// If the name was "jordan", this results in "JORDAN"
{{ user.name | upcase }}
```

### Custom Liquid filters

To make it easier to express dates & countdowns we've added several non-standard filters to our Liquid engine.

#### `date_add_*` and `date_subtract_*`

These filters add or subtract a specified amount of time to/from a date.

| Filter                  | Description                                           |
| ----------------------- | ----------------------------------------------------- |
| `date_add_seconds`      | Adds the specified number of seconds to a date        |
| `date_add_minutes`      | Adds the specified number of minutes to a date        |
| `date_add_hours`        | Adds the specified number of hours to a date          |
| `date_add_days`         | Adds the specified number of days to a date           |
| `date_add_weeks`        | Adds the specified number of weeks to a date          |
| `date_add_months`       | Adds the specified number of months to a date         |
| `date_add_years`        | Adds the specified number of years to a date          |
| `date_subtract_seconds` | Subtracts the specified number of seconds from a date |
| `date_subtract_minutes` | Subtracts the specified number of minutes from a date |
| `date_subtract_hours`   | Subtracts the specified number of hours from a date   |
| `date_subtract_days`    | Subtracts the specified number of days from a date    |
| `date_subtract_weeks`   | Subtracts the specified number of weeks from a date   |
| `date_subtract_months`  | Subtracts the specified number of months from a date  |
| `date_subtract_years`   | Subtracts the specified number of years from a date   |

**Example Usage**:

```liquid
{{ "2024-08-06T07:16:26.802Z" | date_add_minutes: 30 | date: "%s" }}
```

Output: `1722929186`

#### `countdown_*_partial`

These filters calculate the partial difference between two dates in various units. This is usefull for
formatting a countdown timer by exach segment.

| Filter                      | Description                                                                   |
| --------------------------- | ----------------------------------------------------------------------------- |
| `countdown_minutes_partial` | Returns the remaining minutes in the current hour                             |
| `countdown_hours_partial`   | Returns the remaining hours in the current day                                |
| `countdown_days_partial`    | Returns the remaining days in the current week                                |
| `countdown_weeks_partial`   | Returns the remaining weeks in the current month (assuming 4 weeks per month) |
| `countdown_months_partial`  | Returns the remaining months in the current year                              |
| `countdown_years`           | Returns the full number of years between the two dates                        |

**Example Usage**:

```liquid
{{ "2024-08-06T07:16:26.802Z" | countdown_hours_partial: "2024-08-06T15:30:00.000Z" }}:{{  "2024-08-06T07:16:26.802Z" | countdown_minutes_partial: "2024-08-06T15:30:00.000Z" }}
```

Output: `8:33`

#### `countdown_*_total`

These filters calculate the total difference between two dates in various units.

| Filter                    | Description                                           |
| ------------------------- | ----------------------------------------------------- |
| `countdown_minutes_total` | Returns the total number of minutes between two dates |
| `countdown_hours_total`   | Returns the total number of hours between two dates   |
| `countdown_days_total`    | Returns the total number of days between two dates    |
| `countdown_weeks_total`   | Returns the total number of weeks between two dates   |
| `countdown_months_total`  | Returns the total number of months between two dates  |
| `countdown_years_total`   | Returns the total number of years between two dates   |

**Example Usage**:

```liquid
{{ "2024-08-06T07:16:26.802Z" | countdown_days_total: "2024-08-16T07:16:26.802Z" }}
```

Output: `10`

> **Note:** All these filters expect date strings as arguments. Anything that Javascript's default date
> utility can parse will work. For countdown filters, the first argument is the starting date, and
> the second argument (where applicable) is the end date.

> **Note:** The `countdown_*_total` filters calculate the total difference, while the `countdown_*_partial`
> filters calculate the remainder after dividing by the next larger unit (except for years).

> **Note:** For `countdown_months_total` and `countdown_years_total`, the calculations account for varying
> month lengths and leap years.

> **Note:** All countdown filters assume that the end date is later than the start date. If this isn't always
> the case in your application, you may need to handle this scenario separately.

### Snippets with variables

If you create a group of components built off of variables and conditions, save it as a [snippet](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-layout#snippets) to reuse. There are several stock snippets built this way. For example the **Product Selector** snippet:

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/pe-editor-variables-snip1.png)

Adding this snippet shows your products in a vertical stack:

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/pe-editor-variables-snip2.png)

When one is selected, the checkmark next to it is filled in. This is achieved with stock variables (i.e. the selected product) and then changing layer opacity based on if the checkmark's corresponding product is selected or not:

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/pe-editor-variables-snip3.png)

### Testing and handling different states

Often times, you'll want to test things like introductory or trial offers, a certain page within a paging design, or keep a modal drawer open to tweak its contents or look. To do that, simply change the variable's value in the editor. On the left sidebar, click **Variables** and then search for the one you're after and set its value.

Here are some common examples:

1. **Testing introductory offers:** To test trial or introductory offer states, change the `products.hasIntroductoryOffer` to `true` or `false`. In the example below, the text on the paywall changes based on whether or not a trial is available. To easily test it, you can toggle `products.hasIntroductoryOffer`:

<br />

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/docsScrollIntroOffer.gif)

<br />

2. **Testing abandoned transaction states:** To test a drawer or offer that appears after a canceled purchase, change `state.didAbandonTransaction` to `true`. If your copy references the abandoned product, choose a product by setting `products.abandonedProductId` to its product reference, such as `primary` or `secondary`.

3. **Testing a particular page in a paging paywall:** In this design, there are three distinct pages:

<br />

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/togglePages1.png)

By default, the first one is showing. Though, if you needed to easily edit the second or third page, start by finding the variable that is controlling which page is shown. Typically, it'll be a state variable. Here, it's `changeScreen`:

<br />

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/togglePages2.png)

By changing it, you can easily pull up each individual page and edit it as needed:

<br />

![](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/togglePages3.png)