CodeWorlds
Back to collections
Guide17 min readCodeWorlds Team

SuperTokens, open core and paid add-ons

SuperTokens is Apache 2.0 plus an ee directory under a proprietary licence. Which features need a paid key even on your own servers, and what they actually cost.

SuperTokens, open core and paid add-ons

SuperTokens solves authentication with a three-part architecture: a separate service called the core, a library in your backend, and a library in your frontend. The core is licensed under Apache 2.0, but the ee directory in the same repository carries a proprietary licence, and several features you reach for fairly early live exactly there.

Three moving parts instead of one library

The core is a Java program listening on port 3567 by default. It holds users, passwords, sessions and roles in a database that it migrates itself at startup. Since release 11.0.0 only PostgreSQL version 13.0 or newer is supported; MySQL and MongoDB were removed, so if you remember older documentation entries, they no longer apply. More about the database itself is in PostgreSQL.

The second part is the backend SDK. For Node that is the supertokens-node package, which exposes a set of routes under a shared prefix inside your application and talks to the core over HTTP. Every request to the core carries a cdi-version header, and if the core has api_keys configured, an api-key header as well. The third part is the frontend SDK: supertokens-auth-react with ready-made login components, or supertokens-web-js if you write the interface yourself.

The deployment documentation calls the core a trusted backend component and explicitly recommends keeping it on a private network reachable only by your backend, never by the browser. That is not decorative advice: a core without an API key lets anyone who can reach it list and modify users.

The maintenance cost of this arrangement deserves an honest count. It is an extra container in your infrastructure, an extra database or an extra schema in an existing one, migrations run by the core at every start, and three places where versions have to line up. A library that lives in the same process as your application, such as Better Auth, does not have this problem at all.

Versions, licence and what the published packages contain

I checked the licence from three sides, because the declaration in a package registry is not enough.

In the npm registry the supertokens-node package has version 24.0.3 published on 24 July 2026, with the license field set to Apache-2.0. supertokens-auth-react is at 0.51.3 from 30 July 2026, supertokens-web-js at 0.16.0 from 15 August 2025, and supertokens-website at 20.1.6 from 21 March 2025, all with the same licence field. On PyPI supertokens-python is at 0.31.3 from 6 May 2026 and declares Apache 2.0 together with the OSI classifier.

In the repository the licence file is called LICENSE.md, not LICENSE, and fetching the latter name returns a 404. Its content is more interesting than a plain Apache text, because it opens with a split: everything under the ee/ directory falls under a separate file, ee/LICENSE.md, and the rest is Apache 2.0. That separate file is the SuperTokens Enterprise License. It states plainly that software in the ee directory may be used in production only with a valid subscription for the correct number of seats, that copying and modifying for development and testing is allowed without a subscription, and that copying, publishing, distributing and selling are forbidden. This is a proprietary licence, not an open one, and it sits in the same source tree as everything else.

The third source is what the published packages contain, since that is where other projects most often spring surprises. The supertokens-node-24.0.3.tgz archive weighs about 554 kB, holds 1015 entries including 397 .js files, plus a package/LICENSE.md file with an Apache 2.0 header and a VRAI Labs copyright note from 2021. The supertokens-auth-react archive at 0.51.3 also carries package/LICENSE.md with the same note, dated 2020, and 82 .js files. In other words: both packages contain real code and a real licence file, the declarations match, and there is no placeholder here.

The project is active in a way that shows in the release feed. I checked the dates one by one, because this is easy to get wrong: the v12.1.1 entry is dated 13 August 2026 and v12.1.0 is dated 12 August, a day earlier rather than the same day. On 6 August 2026, however, four releases landed at once, v12.0.10, v12.0.9, v11.4.7 and v11.3.7, spread over just under seven hours. Maintaining several lines at once and backporting fixes to older branches is a good signal for anyone who does not want to upgrade the core every month.

One dependency detail deserves a mention: supertokens-node 24.0.3 pulls in fifteen packages, among them twilio at ^4.19.3 and nodemailer at ^8.0.2. They land in node_modules whether or not you send SMS or email through those channels.

What is free and what needs a paid licence

This is the heart of the matter and the most common surprise during a rollout. The list of features covered by the commercial licence is not a question of interpretation, because it sits in the core source as the EE_FEATURES enum. In release v12.1.1 it contains the values account_linking, multi_tenancy, test, dashboard_login, mfa, security, oauth and saml.

Feature in EE_FEATURESWhat it isPrice on the pricing page
mfamulti-factor authentication, TOTP, OTP, passkeys0.01 USD per MAU, minimum 100 USD per month
account_linkinglinking accounts belonging to the same user0.005 USD per MAU, minimum 100 USD per month
dashboard_loginadmin dashboard beyond three accounts20 USD per account per month
multi_tenancymulti-tenancy and organisation supportno figure, a "See pricing" link
oauthyour application acting as an OAuth 2.0 providerno figure, "Contact us"
samlSAML loginno figure, absent from the add-on list
securityAttack Protection Suiteno figure, "Contact us"

Plenty stays on the free side, and that is the fair part of the arrangement. Password login, social login and arbitrary custom OpenID providers, passwordless login by magic link and by one-time code over email or SMS, address verification, password reset, cookie-based session management, roles and permissions, the prebuilt login interface and the SDK override mechanism. The admin dashboard is free too, but for three accounts: the MAX_NUMBER_OF_FREE_DASHBOARD_USERS constant in the Dashboard class is set to 3, and adding a fourth account without a key raises FeatureNotEnabledException.

Two things in this picture are awkward. First, saml is in the paid feature enum, yet the pricing page never lists it among priced add-ons, while the competitor comparison table shows "SAML Login: Yes". You cannot read from the vendor's own material what it costs. Second, the core source contains an ENTERPRISE_THIRD_PARTY_IDS array with the values google-workspaces, okta, active-directory and boxy-saml, used when counting paid feature usage statistics. If you plan corporate login through Okta or Active Directory, that is exactly the area where billing stops being free.

Documentation for paid features marks them with a "This feature is only available to paid users" callout and a "View Details" button. The content behind that button renders only in a browser, so fetching the page with a command line tool shows no price at all. If you want numbers, you have to open the pricing page.

The licence key in practice

The key is handed to the core through the /ee/license API. A PUT with a licenseKey field in the body stores the key and immediately synchronises the feature list, GET returns the currently configured key, and DELETE removes it. The call is scoped to an application and can be made only from the public tenant.

Code
Bash
# set the licence key on the core
curl -X PUT http://127.0.0.1:3567/ee/license \
  -H 'Content-Type: application/json' \
  -H 'api-key: YOUR_CORE_API_KEY' \
  -d '{"licenseKey":"..."}'

# check what the core has stored
curl -X GET http://127.0.0.1:3567/ee/license \
  -H 'api-key: YOUR_CORE_API_KEY'

# remove the key and fall back to the open features
curl -X DELETE http://127.0.0.1:3567/ee/license \
  -H 'api-key: YOUR_CORE_API_KEY'

Key verification takes one of two paths, and the difference matters for an air-gapped deployment. The doesLicenseKeyRequireServerQuery method simply checks whether the key split on dots has three parts. If it does, it is a JWT and the core verifies it locally with an RSA public key compiled into the source. If it does not, the core sends a POST request to https://api.supertokens.com/0/st/license/check.

The content of that request is visible in the code: telemetryId, licenseKey, superTokensVersion and a paidFeatureUsageStats object. The last one carries usage counters, for example user_count for the admin dashboard and statistics for MFA, multi-tenancy and account linking. The INTERVAL_BETWEEN_SERVER_SYNC constant is 3600 * 24 seconds, so the sync repeats once a day, and INTERVAL_BETWEEN_DB_READS is four hours, after which the core re-reads the stored flag state. The result lands in the database under the LICENSE_KEY and FEATURE_FLAG keys.

The practical conclusion: self-hosting does not automatically mean the core stays silent. With a JWT-format key it does. With a key that does not look like a JWT it reaches out once a day and sends usage statistics together with a telemetry identifier. If you work on a network with no route to the outside world, that is a question to put to the vendor before signing, not after.

Pricing and arithmetic

The pricing page separates two variants. In the cloud you pay 0.02 USD per monthly active user, and below five thousand such users it is free. The self-hosted variant is described as free and open source with no user limit, which applies solely to the open features.

ItemCloudSelf-hosted
Base0.02 USD per MAU, free below 5000 MAUfree, no MAU limit
MFA0.01 USD per MAU, minimum 100 USD per monthsame rate and same minimum
Account linking0.005 USD per MAU, minimum 100 USD per monthsame rate and same minimum
Dashboard accounts20 USD per account, first three freesame rate
Multi-tenancyno figure givenno figure given

I checked the arithmetic in the page's examples and it holds. For 5000 users with account linking enabled it comes to (0.02 + 0.005) * 5000 = 125 USD per month, and for 7000 users (0.02 + 0.005) * 7000 = 175 USD. The quoted 100 USD per month for the range from 1 to 4999 users also holds, because the add-on billing minimum applies there.

Watch the free threshold: it is a cliff, not a deduction. At 4999 users the cloud base costs zero, at 5000 you pay 0.02 USD for all five thousand, meaning 100 USD, and that is precisely how the vendor's example computes it. The second thing is the 100 USD monthly minimum on MFA and on account linking. It means the cheapest MFA scenario on your own server, with fifty users, costs 100 USD per month, or 1200 USD per year. That surprises people who picked SuperTokens specifically to avoid paying per MAU.

There is also a discrepancy inside the page itself. The calculator has a "Multitenancy" row showing 0 USD in both columns, while the paid feature list sends you to "See pricing" for multi-tenancy without a figure. Multi-tenancy is in EE_FEATURES, so it will not work without a key, but no price can be read from the vendor's material. The questions section on the pricing page adds that above ten thousand monthly active users, or more than five organisations as customers, a discount can be negotiated, again with no rates given.

Running it from scratch

The core is easiest to stand up from a container image. The documentation names the image supertokens/supertokens-postgresql, while the README of the image project refers to registry.supertokens.io/supertokens/supertokens-postgresql.

Code
Bash
# core with your own PostgreSQL database
docker run -p 3567:3567 -d \
  -e POSTGRESQL_CONNECTION_URI="postgresql://user:pass@db:5432/supertokens" \
  -e API_KEYS="long-random-string" \
  registry.supertokens.io/supertokens/supertokens-postgresql

# check that the core came up
curl http://127.0.0.1:3567/hello

Without the POSTGRESQL_USER, POSTGRESQL_PASSWORD, POSTGRESQL_PASSWORD_FILE and POSTGRESQL_CONNECTION_URI variables the core starts on an in-memory database. The demo works, login works, and after a container restart there is not a single user left. The same README adds that at startup the core waits up to about an hour for PostgreSQL to become available, so a container that looks hung may simply be waiting for the database.

Configuration can also come from a file. Inside the container it lives at /usr/lib/supertokens/config.yaml and accepts fields such as these:

Code
YAML
# fragment of the core config.yaml
port: 3567
host: "0.0.0.0"
api_keys: "long-random-string"
access_token_validity: 3600          # seconds
refresh_token_validity: 144000       # minutes, not seconds
password_hashing_alg: "BCRYPT"       # or "ARGON2"
bcrypt_log_rounds: 11
ip_allow_regex: "10\\.0\\..*"
log_level: "INFO"
disable_telemetry: true

The backend initialises once, at application startup. The TypeInput type in supertokens-node accepts the fields supertokens, framework, appInfo, recipeList, telemetry, isInServerlessEnv and debug. The framework field accepts the values express, fastify, hapi, loopback, koa, awsLambda and custom.

Code
TypeScript
import supertokens from "supertokens-node"
import Session from "supertokens-node/recipe/session"
import EmailPassword from "supertokens-node/recipe/emailpassword"
import Dashboard from "supertokens-node/recipe/dashboard"

supertokens.init({
  framework: "express",
  supertokens: {
    connectionURI: process.env.SUPERTOKENS_CONNECTION_URI!,
    apiKey: process.env.SUPERTOKENS_API_KEY
  },
  appInfo: {
    appName: "code-worlds",
    apiDomain: "https://api.example.com",
    websiteDomain: "https://example.com",
    apiBasePath: "/auth",
    websiteBasePath: "/auth"
  },
  recipeList: [EmailPassword.init(), Session.init(), Dashboard.init()],
  telemetry: false
})

The frontend gets a nearly identical appInfo object, which is convenient, since both sides have to agree on the route prefixes. If you build on Next.js, remember that apiDomain and websiteDomain will be the same address.

Code
TypeScript
import SuperTokens from "supertokens-auth-react"
import Session from "supertokens-auth-react/recipe/session"
import EmailPassword from "supertokens-auth-react/recipe/emailpassword"

SuperTokens.init({
  appInfo: {
    appName: "code-worlds",
    apiDomain: "https://api.example.com",
    websiteDomain: "https://example.com",
    apiBasePath: "/auth",
    websiteBasePath: "/auth"
  },
  recipeList: [EmailPassword.init(), Session.init()]
})

Version compatibility between the core and the SDKs is a genuine trap during upgrades, because you upgrade three things and all three have to line up. The vendor publishes a compatibility table at /docs/references/compatibility-table, but its version lists load only in a browser. The page fetched without executing JavaScript shows just the headings, "Compatible Backend SDK Versions", "SuperTokens Core Version" and "Compatible Frontend SDK Versions", without a single number. The table exists, but it cannot be checked by a script in a continuous integration pipeline. The mechanism underneath is interface negotiation: requests carry a cdi-version header, and the core configuration has supertokens_max_cdi_version and supertokens_min_cdi_version fields.

SuperTokens against the alternatives

ToolModelWhere user data livesWhat you pay for
SuperTokensopen core, a separate service plus two SDKswith you or in the vendor cloud0.02 USD per MAU in the cloud, paid add-ons even self-hosted
Better Autha library inside your process, version 1.7.1 under MITalways with younothing for the library itself
Clerkclosed servicewith the vendorper active user
Auth0closed servicewith the vendorper active user and per feature
WorkOSclosed servicewith the vendorper identity provider connection

The place SuperTokens occupies on this map is fairly specific. If the main requirement is that the user database and password hashes never leave your infrastructure, while you also do not want to write password reset and session rotation flows from scratch, it is a good pick. If all you care about is not paying, Better Auth gets the same effect with no extra container and no category of paid features. If instead you value your team's time above the invoice, managed services such as Clerk, Auth0 or Kinde save you the entire operational layer, at the cost of handing over the data and tying yourself to a vendor. A separate case is selling to large companies, where the number of connected identity providers dominates, and there per-connection billing in WorkOS fits better.

Vendor lock-in with SuperTokens is lower than with closed services, but it is not zero. The database schema is yours and you can read it, yet passwords are hashed with the algorithm set in password_hashing_alg, and the session flows assume the core is present. Migrating to something else means moving the hashes and forcing everyone to log in again, not simply swapping a library.

One name in this category needs correcting, because it still circulates in guides. Stack Auth, once listed alongside SuperTokens as an open alternative to Clerk, is called Hexclave today: the repository and the domain redirect to the new branding, and the @stackframe/* packages were replaced by the @hexclave/* family. The licence split there resembles this one, a MIT client and AGPLv3 server components, but with two differences that cut the wrong way: the license field is missing from the metadata of six packages at once, and the copyright notice still carries the old company name.

Common mistakes

The first and most expensive: assuming self-hosting means everything is free. What is open is the core outside the ee directory. MFA, account linking, multi-tenancy, the OAuth 2.0 provider role, SAML and the attack protection suite all require a key even when you pay for the server's electricity yourself.

The second: leaving the in-memory database in place. A container without POSTGRESQL_* variables starts correctly and passes tests, then loses its data on every restart.

The third: exposing the core to the internet. Without api_keys, port 3567 grants full access to users. There are also ip_allow_regex and ip_deny_regex, worth setting even on a private network.

The fourth: upgrading the SDK without the core, or the other way round. The cdi-version header will reject an incompatible combination, and the compatibility table cannot be checked by a script, so this has to be done by hand before deployment.

The fifth: adding a fourth account to the admin dashboard. The MAX_NUMBER_OF_FREE_DASHBOARD_USERS constant is 3, and the extra account costs 20 USD per month.

The sixth: assuming licence verification is local. A JWT-format key is verified offline, any other key triggers a request to api.supertokens.com once a day along with usage statistics.

FAQ

Is self-hosting SuperTokens really free?

Partly. Password login, social login, passwordless login, address verification, sessions, roles and permissions, and the dashboard for up to three accounts are free with no user limit. The features listed in the EE_FEATURES enum require a paid key regardless of where the core runs.

Which features exactly require a licence key?

In release v12.1.1 they are account_linking, multi_tenancy, dashboard_login beyond three accounts, mfa, security, oauth and saml. Explicit prices are given only for MFA, account linking and dashboard accounts. For the rest the vendor points you at a contact form.

Does the core connect to the internet?

It depends on the key format. A three-part key is a JWT verified locally. Any other key triggers a call to https://api.supertokens.com/0/st/license/check once a day, with a telemetry identifier and paid feature usage counters. Telemetry can be switched off separately through disable_telemetry in the core configuration and telemetry: false in the SDK.

Can I use MySQL or MongoDB?

Not in current versions. Support for both was removed in core release 11.0.0. PostgreSQL from version 13.0 upwards is the supported database.

What does the cheapest MFA scenario on my own server cost?

One hundred dollars a month, because that is the minimum billing amount for the MFA add-on regardless of user count. At a rate of 0.01 USD per active user the minimum stops mattering only above ten thousand monthly users.

When is SuperTokens a poor choice?

When you need only password and social login in a small project, because three moving parts are excess complexity. When you work on a network cut off from the internet and are unsure about the key format. And when your product plan assumes multi-tenancy and SAML from day one, because then of the two promised advantages, openness and zero cost, only one survives.

Read next

We use cookies to enhance your experience on the site