Skip to main content

This is a contributors guide and NOT a user guide. Please visit these docs if you are using or evaluating SuperTokens.

Backend SDK

  • 1) All CRUD operations for all objects in the db (as much as makes sense). For example, if we take email verification as a recipe, we want the user to be able to:

    • a) Do the normal email verification flow (by generating the token etc..)
    • b) Set if an email is verified or not (without any token)
    • c) Know if an email is verified or not
  • 2) Should be as decoupled from other recipes as possible so that it can be reused.

  • 3) Should be as minimal as possible.

  • 4) All sub recipes being used should be allowed to be overridden by an instance provided in the constructor. This will allow super recipes to also use these sub recipes without us creating multiple instances of those sub recipes.

  • 5) Recipe interface should follow:

    • a) Have one function per CRUD operation
    • b) If a function has valid error states, those should be returned from the function like {status: "OK", ...} | {status: "ERROR_1", ...} | {status: "ERROR_2", ...}. That is, no throwing or errors unless it's a general error, or the type system for that language allows you to clearly define error types.
    • c) If the input to the function is a request or response object (as in the case of session recipe), then it should use the abstracted request / response object and not a web framework specific one. Then the web framework specific implementation will override that function which users can then use.
    • d) The input to each function should be as minimal as possible, but also allow for as much user customisation as possible.
  • 6) API interface should follow:

    • a) They should use the abstracted request / response objects and not actually any framework specific objects.

    • b) Valid error states should be returned as a 200 response. So the response should look like {status: "OK", ...} | {status: "ERROR_1", ...} | {status: "ERROR_2", ...}.

    • c) The implementation of the API should only use functions from the input param, or from other recipes that can be imported by the user without going through the build folder. This allows users to copy / paste API implementations and modify them as per their needs.

    • d) There should be a one-to-one mapping between the APIs and the API interface functions for clarity of usage. The API interface function can have a name to further clarify the usage of it. For example, in thirdpartyemailpassword, we have one API for emailpass sign in, one for emailpass sign up, and one for third party sign in / up. This is done instead of combining them into one API, since there are actually three different API paths.

    • e) All APIs must have a GENERIC_ERROR response so that users show a UI message for their custom errors.

    • f) Any new object / entity created during the API must be returned from it. These can range from a new session, to a new user, to new tokens etc..

      If a super recipe has two sub recipe APIs whose path and method are the same, then it can split those into two API interface functions, for clarity of use.

  • 7) When doing input parsing for APIs, only check for what we care about. If the user has manually added some other fields to the request body, let them go through

  • 8) There should be one file where all the functions exposed by this recipe are listed and documented. This file will be linked from the docs as API documentation.

    • a) For functions
    • b) For API override interface
    • c) For function override interface
  • 9) Recipe functions should be as isolated as possible.. this means that they should not have to use other recipes (as much as possible).

    For example, in sign up, we want to first create a new user in the core, and then create a new session. Both of those operations should not happen with one recipe function, and should happen in the api function or the user facing function instead.

    Another example is the delete user function exposed via the SDK. In this, we want to delete all the sessions first (using the session recipe) and then delete the actual user in the core. The recipe function would only do the deleting the user from the core part, however, the function exposed via the SDK (which uses this recipe function), should do the session part first and then the user deletion part.

  • 10) When adding a new web framework support, unless the repo is specifically for that framework, we should not add the framework as a dependency. We can add it as a dev dependency though.

  • 11) When creating a recipe that uses two sub recipes, we must design it in a way that users can switch off any of the sub recipes such that it is equavalent to any of the sub (or sub sub recipes). This is done so that in case of multi tenancy, if someone wants a different type of login method per tenant, they can do that all using just one recipe.

  • 12) About input normalization: If the input is a user defined input (like email, name etc), those need to be normalized inside recipe functions. Each input of a recipe function should have a way of being normalised and the user should be able to override how those normalisation functions behave - on a per function, per input level.

  • 13) User interaction with a recipe should be isolated to that recipe, even if that recipe uses several other sub recipes. For example, if a user initialises recipeX (which uses recipeY and recipeZ underneath), the user should not have to do recipeY.something or recipeZ.something. Therefore, recipeX must re-export useful functions that are provided by recipeY and recipeZ.

  • 14) About User facing functions:

    • a) We are allowed to call multiple recipe functions from several recipes in these. For example, in the delete user function (user facing function), we want to revoke all sessions, delete the email verification entries etc..
    • b) These should be a one to one mapping with the recipe functions as much as possible.
  • 15) If a recipe does not create users, then it must accept a doesUserExist function so that it can take into account deletion of users in its logic. See Note that only those recipes that can "continuously be used per user, per object of recipe" would need to do this.

    For example, in the session recipe, once a session is created for a user, that user can keep refreshing it to keep the session alive forever. Therefore, to stop this continuous refreshing behaviour, we need to check if the user still exists. We don't need to check this during session creation since that's not a continuous operation per user, per session (object of recipe is session), and we assume that that function will only be called if we know that a user exists (there is a race condition in which we may create a new session post deletion of user, but that will be stopped in the refresh API call by using the doesUserExist function).

    On the other hand, in the case of email verification recipe, once a verification token is created, it can be redeemed only once and that's it. There is no continuous behaviour per user, per verification token (the object of this recipe). We don't need to check if a user exists during token creation time since even if they don't (due to race condition), an extra row will remain in the db for that non existent userId and will eventually get removed cause it expired (cause no continuous operation per user per object of the recipe).

  • 16) When implementing API / Recipe interfaces, make sure that if a function uses another function in the interface, it will call the user's overrided verion of that function (if the user has overriden it). See

  • 17) All functions (API, recipe interface and user facing functions) must accept an optional context object which can be utilised by the user to put any info into. We should never add anything to thise object and always treat this empty. An example use case of this is to pass the input formFields from the API to the createNewSession function.

Looking for older versions of the documentation?
Which UI do you use?
Custom UI
Pre built UI