---
title: Function Overrides
description: Override backend authentication functions to cover custom use cases.
sidebar:
  order: 20
---

## Overview

**Function overrides** let you customize the behavior of the functions used internally, by the SDKs.
You can change how actions like signing in, signing up, creating, or revoking sessions or signing out work.
This flexibility lets you integrate your own logic into the authentication and session management processes.

For example, if a recipe checks for an active session using the session recipe's `doesSessionExist` function, you can override that function to work with custom session management.
Similarly, if you already have a sign-in/sign-up flow and want to integrate with SuperTokens, you can use an override to handle the migration process.
You can even implement a custom `userId` format by mapping `userIds` to those generated by SuperTokens.


## Example


The code snippet shows the general flow of overriding a function. 
Custom logic can be injected while also calling the original implementation of the function.

<DependentContent passive group="backend-language">
<ContentOption title="Node.js" value="nodejs">
:::info
See all the [functions that can be overridden here](https://supertokens.com/docs/references/backend-sdks/function-overrides)
:::
</ContentOption>
<ContentOption title="Go" value="go">
:::info
See all the [functions that can be overridden here](https://pkg.go.dev/github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels#RecipeInterface)
:::
</ContentOption>
<ContentOption title="Python" value="python">
:::info
See all the [functions that can be overridden here](https://supertokens.com/docs/references/backend-sdks/function-overrides)
:::
</ContentOption>
</DependentContent>

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```tsx
import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import ThirdParty from "supertokens-node/recipe/thirdparty";

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  supertokens: {
    connectionURI: "...",
  },
  recipeList: [
    Session.init({
      override: {
        functions: (originalImplementation) => {
          return {
            ...originalImplementation,

            // here we are only overriding the function that's responsible
            // for creating a new session
            createNewSession: async function (input) {
              // TODO: some custom logic

              // or call the default behaviour as show below
              return await originalImplementation.createNewSession(input);
            },
            // ...
            // TODO: override more functions
          };
        },
      },
    }),
    ThirdParty.init({
      signInAndUpFeature: {
        providers: [
          /* ... */
        ],
      },
      override: {
        functions: (originalImplementation) => {
          return {
            ...originalImplementation,

            // here we are only overriding the function that's responsible
            // for signing in or signing up a user.
            signInUp: async function (input) {
              // TODO: some custom logic

              // or call the default behaviour as show below
              return await originalImplementation.signInUp(input);
            },
            // ...
            // TODO: override more functions
          };
        },
      },
    }),
  ],
});
```
</Tab>
<Tab title="Go" value="go">
```go
import (
	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
	"github.com/supertokens/supertokens-golang/recipe/thirdparty"	
	"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
	"github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
	supertokens.Init(supertokens.TypeInput{
		RecipeList: []supertokens.Recipe{
			session.Init(&sessmodels.TypeInput{
				Override: &sessmodels.OverrideStruct{
					Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
						// First we make a copy of the original implementation
						originalCreateNewSession := *originalImplementation.CreateNewSession

						// Then we override the default impl
						(*originalImplementation.CreateNewSession) = func(userID string, accessTokenPayload, sessionDataInDatabase map[string]interface{}, disableAntiCsrf *bool, tenantId string, userContext supertokens.UserContext) (sessmodels.SessionContainer, error) {
							// TODO: some custom logic

							// or call the default behaviour as show below
							return originalCreateNewSession(userID, accessTokenPayload, sessionDataInDatabase, disableAntiCsrf, tenantId, userContext)
						}

						return originalImplementation
					},
				},
			}),
			thirdparty.Init(&tpmodels.TypeInput{
				Override: &tpmodels.OverrideStruct{
					Functions: func(originalImplementation tpmodels.RecipeInterface) tpmodels.RecipeInterface {

						//First we copy the original impl
						originalSignInUp := *originalImplementation.SignInUp

						// Then we override the functions we want to
						(*originalImplementation.SignInUp) = func(thirdPartyID string, thirdPartyUserID string, email string, oAuthTokens map[string]interface{}, rawUserInfoFromProvider tpmodels.TypeRawUserInfoFromProvider, tenantId string, userContext *map[string]interface{}) (tpmodels.SignInUpResponse, error) {
							// TODO: some custom logic

							// or call the default behaviour as show below
							return originalSignInUp(thirdPartyID, thirdPartyUserID, email, oAuthTokens, rawUserInfoFromProvider, tenantId, userContext)
						}

						// TODO: Override more functions

						return originalImplementation
					},
				},
			}),
		},
	})
}
```
</Tab>
<Tab title="Python" value="python">
```python
from typing import Any, Dict, Optional, Union

from supertokens_python import InputAppInfo, SupertokensConfig, init
from supertokens_python.recipe import session, thirdparty
from supertokens_python.recipe.session.interfaces import (
    RecipeInterface as SessionRecipeInterface,
)
from supertokens_python.recipe.session.interfaces import SessionContainer
from supertokens_python.recipe.thirdparty.interfaces import (
    RecipeInterface as ThirdPartyRecipeInterface,
)
from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider
from supertokens_python.types import RecipeUserId


def override_thirdparty_functions(original_implementation: ThirdPartyRecipeInterface):
    original_sign_in_up = original_implementation.sign_in_up

    async def sign_in_up(
        third_party_id: str,
        third_party_user_id: str,
        email: str,
        is_verified: bool,
        oauth_tokens: Dict[str, Any],
        raw_user_info_from_provider: RawUserInfoFromProvider,
        session: Optional[SessionContainer],
        should_try_linking_with_session_user: Union[bool, None],
        tenant_id: str,
        user_context: Dict[str, Any],
    ):
        # TODO: custom logic

        # or call the default behaviour as show below
        return await original_sign_in_up(
            third_party_id,
            third_party_user_id,
            email,
            is_verified,
            oauth_tokens,
            raw_user_info_from_provider,
            session,
            should_try_linking_with_session_user,
            tenant_id,
            user_context,
        )

    original_implementation.sign_in_up = sign_in_up
    return original_implementation

def override_session_functions(original_implementation: SessionRecipeInterface):
    original_create_new_session = original_implementation.create_new_session

    async def create_new_session(
        user_id: str,
        recipe_user_id: RecipeUserId,
        access_token_payload: Optional[Dict[str, Any]],
        session_data_in_database: Optional[Dict[str, Any]],
        disable_anti_csrf: Optional[bool],
        tenant_id: str,
        user_context: Dict[str, Any],
    ):
        # TODO: custom logic

        # or call the default behaviour as show below
        return await original_create_new_session(
            user_id,
            recipe_user_id,
            access_token_payload,
            session_data_in_database,
            disable_anti_csrf,
            tenant_id,
            user_context,
        )

    original_implementation.create_new_session = create_new_session
    return original_implementation


init(
    supertokens_config=SupertokensConfig(connection_uri="..."),
    app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
    framework="fastapi",
    recipe_list=[
        session.init(
            override=session.InputOverrideConfig(functions=override_session_functions)
        ),
        thirdparty.init(
            override=thirdparty.InputOverrideConfig(
                functions=override_thirdparty_functions
            ),
            sign_in_and_up_feature=thirdparty.SignInAndUpFeature(
                providers=[
                    # ...
                ]
            ),
        )
    ],
)
```
</Tab>
</CodeGroup>

---

## Error management

If you want to throw a custom error from function overrides you have to handle it manually.

### Raise the error

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```ts
import Session from "supertokens-node/recipe/session";

Session.init({
  override: {
    functions: (originalImplementation) => {
      return {
        ...originalImplementation,
        createNewSession: async function (input) {
          const existingSessions = await Session.getAllSessionHandlesForUser(input.userId);
          if (existingSessions.length > 0) {
            // this means that the user already has a session on some other device
            throw new Error("Session already exists on another device");
          }

          // no other session exists, and so we can continue with logging in this user
          return originalImplementation.createNewSession(input);
        },
      };
    },
  },
});
```
</Tab>
<Tab title="Go" value="go">
```go
import (
	"errors"

	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
	"github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
	session.Init(&sessmodels.TypeInput{
		Override: &sessmodels.OverrideStruct{
			Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {

				// first we copy the original implementation
				originalCreateNewSession := *originalImplementation.CreateNewSession

				(*originalImplementation.CreateNewSession) = func(userID string, accessTokenPayload, sessionDataInDatabase map[string]interface{}, disableAntiCsrf *bool, tenantId string, userContext supertokens.UserContext) (sessmodels.SessionContainer, error) {
					existingSessions, err := session.GetAllSessionHandlesForUser(userID, &tenantId, userContext)
					if err != nil {
						return nil, err
					}

					if len(existingSessions) > 0 {
						// this means that the user already has a session on some other device
						return nil, errors.New("Session already exists on another device")
					}

					// no other session exists, and so we can continue with logging in this user
					return originalCreateNewSession(userID, accessTokenPayload, sessionDataInDatabase, disableAntiCsrf, tenantId, userContext)
				}

				return originalImplementation

			},
		},
	})
}
```
</Tab>
<Tab title="Python" value="python">
```python
from typing import Any, Dict, Optional

from supertokens_python.recipe import session
from supertokens_python.recipe.session.asyncio import get_all_session_handles_for_user
from supertokens_python.recipe.session.interfaces import RecipeInterface
from supertokens_python.types import RecipeUserId


def override_session_functions(original_implementation: RecipeInterface):

    # first we copy the original implementation
    original_create_new_session = original_implementation.create_new_session

    async def create_new_session(
        user_id: str,
        recipe_user_id: RecipeUserId,
        access_token_payload: Optional[Dict[str, Any]],
        session_data_in_database: Optional[Dict[str, Any]],
        disable_anti_csrf: Optional[bool],
        tenant_id: str,
        user_context: Dict[str, Any],
    ):
        existing_sessions = await get_all_session_handles_for_user(user_id)

        if len(existing_sessions) > 0:
            # this means that the user already has a session on some other device
            raise Exception("Session already exists on another device")

        # no other session exists, and so we can continue with logging in this user
        return await original_create_new_session(
            user_id,
            recipe_user_id,
            access_token_payload,
            session_data_in_database,
            disable_anti_csrf,
            tenant_id,
            user_context,
        )

    original_implementation.create_new_session = create_new_session

    return original_implementation


session.init(override=session.InputOverrideConfig(functions=override_session_functions))
```
</Tab>
</CodeGroup>

### Handle the error manually

<DependentContent passive group="backend-language">
<ContentOption title="Node.js" value="nodejs">
<DependentContent passive group="node-frameworks">
<ContentOption title="Next.js" value="nextjs">
<NextjsRouterTypeSelect />
</ContentOption>
</DependentContent>
</ContentOption>
</DependentContent>

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
<DependentContent group="node-frameworks" label="Node.js framework">
<ContentOption title="Express" value="express">
```ts
import express from "express";

let app = express();

//...

// in your app's error handler, we catch the custom error
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
  if (err.message === "Session already exists on another device") {
    // TODO: send a custom response using res
    return;
  }
  res.status(500).send(err.message);
});
```
</ContentOption>
<ContentOption title="Hapi" value="hapi">
```ts
import Hapi from "@hapi/hapi";
let server = new Hapi.Server({ port: 8000 });

// first we create a plugin to handle all errors from the app
const plugin = {
  name: "...",
  version: "...",
  register: async function (server: Hapi.Server) {
    server.ext("onPreResponse", async (request, h) => {
      if ("isBoom" in request.response) {
        let err = request.response.data;
        if (err.message === "Session already exists on another device") {
          // TODO: send a custom response here with takeover
        }
      }
      return h.continue;
    });
  },
};

// then we register this plugin
(async () => {
  await server.register(plugin);

  await server.start();
})();
```
</ContentOption>
<ContentOption title="Fastify" value="fastify">
```tsx
import Fastify from "fastify";

let fastify = Fastify();

fastify.setErrorHandler(async (err: any, req, res) => {
  if (err.message === "Session already exists on another device") {
    // TODO: send a custom response here with takeover
  }
  // TODO: send a 500 error with the err.message
});
```
</ContentOption>
<ContentOption title="Aws Lambda" value="aws-lambda">
```ts check=false reason="AWS Lambda handler imports application-local backend configuration"
import middy from "@middy/core";
import cors from "@middy/http-cors";
import SuperTokens from "supertokens-node";
// this is in the auth.js file
import { middleware } from "supertokens-node/framework/awsLambda";
import { getBackendConfig } from "./config";

module.exports.handler = middy(middleware())
  .use(
    cors({
      origin: getBackendConfig().appInfo.websiteDomain,
      credentials: true,
      headers: ["Content-Type", ...SuperTokens.getAllCORSHeaders()].join(", "),
      methods: "OPTIONS,POST,GET,PUT,DELETE",
    }),
  )
  .onError((request) => {
    if (request.error !== null && request.error.message === "Session already exists on another device") {
      // TODO: send a custom response here with takeover
    }
    throw request.error;
  });
```
</ContentOption>
<ContentOption title="Koa" value="koa">
```ts
import Koa from "koa";
import { middleware } from "supertokens-node/framework/koa";

let app = new Koa();

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err: any) {
    if (err.message === "Session already exists on another device") {
      // TODO: return a custom response
    }
    throw err;
  }
});

app.use(middleware());
```
</ContentOption>
<ContentOption title="LoopBack" value="loopback">
```ts
import { Next } from "@loopback/core";
import { RestApplication, Middleware, MiddlewareContext } from "@loopback/rest";
import { middleware } from "supertokens-node/framework/loopback";

let app = new RestApplication();

export const customErrorMiddleware: Middleware = async (ctx: MiddlewareContext, next: Next) => {
  try {
    return await next();
  } catch (err: any) {
    if (err.message === "Session already exists on another device") {
      // TODO: return a custom response
    }
    throw err;
  }
};

app.middleware(middleware);
app.middleware(customErrorMiddleware);
```
</ContentOption>
<ContentOption title="Next.js" value="nextjs">
<ConditionalContent propertyName="nextjsRouterType" condition="pages-router">

```ts
import { superTokensNextWrapper } from "supertokens-node/nextjs";
import { middleware } from "supertokens-node/framework/express";

// in the /auth/[[...path]].tsx file
export default async function superTokens(req: any, res: any) {
  //...
  try {
    await superTokensNextWrapper(
      async (next) => {
        // Refer to the Next.js integration guide to know why this is needed
        res.setHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
        await middleware()(req, res, next);
      },
      req,
      res,
    );
  } catch (err: any) {
    if (err.message === "Session already exists on another device") {
      // TODO: send custom reply
    }
    throw err;
  }

  //...
}
```

</ConditionalContent>
</ContentOption>
<ContentOption title="Nestjs" value="nestjs">
```tsx
import { ExceptionFilter, Catch, ArgumentsHost } from "@nestjs/common";

import { errorHandler } from "supertokens-node/framework/express";
import { Error as STError } from "supertokens-node";

// we want to add our own error handler which will catch the special exception

@Catch(STError)
export class AppErrorHandler implements ExceptionFilter {
  catch(exception: Error, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    if (exception.message === "Session already exists on another device") {
      // TODO: send custom error using ctx.getResponse<Response>()
    } else {
      throw exception;
    }
  }
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Go" value="go">
```go
import (
	"net/http"

	"github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
	supertokens.Init(supertokens.TypeInput{
		OnSuperTokensAPIError: func(err error, req *http.Request, res http.ResponseWriter) {
			if err.Error() == "Session already exists on another device" {
				// TODO: send custom error
			}
			// TODO: send generic error
		},
	})
}
```
</Tab>
<Tab title="Python" value="python">
<DependentContent group="python-frameworks" label="Python framework">
<ContentOption title="FastAPI" value="fastapi">
```python
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(Exception)
async def exception_handler(_: Request, exc: Exception) -> JSONResponse:
    if str(exc) == "Session already exists on another device":
        return JSONResponse({"message": str(exc)}, status_code=409)

    return JSONResponse({"message": "Internal server error"}, status_code=500)
```
</ContentOption>
<ContentOption title="Flask" value="flask">
```python
from flask import Flask

app = Flask(__name__)

@app.errorhandler(Exception)
def all_exception_handler(error: Exception):
    if str(error) == "Session already exists on another device":
        return {"message": str(error)}, 409

    return {"message": "Internal server error"}, 500
```
</ContentOption>
<ContentOption title="Django" value="django">
```python
# Add this middlware in settings.py

from typing import Callable

from django.http import HttpRequest, HttpResponse


class ErrorHandlerMiddleware:

    def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
        self.get_response = get_response

    def __call__(self, request: HttpRequest):
        response = self.get_response(request)
        return response

    def process_exception(self, request: HttpRequest, exception: Exception) -> HttpResponse:
        if exception and str(exception) == "Session already exists on another device":
            pass # TODO: send custom response

        return HttpResponse("Error processing the request.", status=500)
```
</ContentOption>
</DependentContent>
</Tab>
</CodeGroup>

<CodeGroup passive group="backend-language">
<Tab title="Node.js" value="nodejs">
<DependentContent group="node-frameworks" label="Node.js framework">
<ContentOption title="Next.js" value="nextjs">
<ConditionalContent propertyName="nextjsRouterType" condition="app-router">

```ts check=false reason="Next.js route imports application-local backend configuration"
import { getAppDirRequestHandler } from "supertokens-node/nextjs";
import { NextRequest, NextResponse } from "next/server";
import SuperTokens from "supertokens-node";
import { backendConfig } from "@/app/config/backend";

SuperTokens.init(backendConfig());

// in the app/api/auth/[...path]/route.ts file
const handleCall = getAppDirRequestHandler();

const withCustomErrorHandling = async (request: NextRequest) => {
  try {
    return await handleCall(request);
  } catch (err: any) {
    if (err.message === "Session already exists on another device") {
      // TODO: send custom reply
    }
    throw err;
  }
};

export async function GET(request: NextRequest) {
  const res = await withCustomErrorHandling(request);
  if (!res.headers.has("Cache-Control")) {
    // Refer to the Next.js integration guide to know why this is needed
    res.headers.set("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
  }
  return res;
}

export const POST = withCustomErrorHandling;
export const DELETE = withCustomErrorHandling;
export const PUT = withCustomErrorHandling;
export const PATCH = withCustomErrorHandling;
export const HEAD = withCustomErrorHandling;
```


</ConditionalContent>
</ContentOption>
</DependentContent>
</Tab>
</CodeGroup>


---

## Read custom request information

<DependentContent passive group="backend-language">
<ContentOption title="Node.js" value="nodejs">
We use the `getRequestFromUserContext` function provided by the SDK to get the request object from the user context.
</ContentOption>
<ContentOption title="Go" value="go">
We use the `GetRequestFromUserContext` function provided by the SDK to get the request object from the user context.
</ContentOption>
<ContentOption title="Python" value="python">
We use the `get_request_from_user_context` function provided by the SDK to get the request object from the user context.
</ContentOption>
</DependentContent>

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```tsx
import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";

Session.init({
  override: {
    functions: (oI) => {
      return {
        ...oI,
        revokeSession: async (input) => {
          let customHeaderValue = "";
          const request = SuperTokens.getRequestFromUserContext(input.userContext);

          if (request !== undefined) {
            customHeaderValue = request.getHeaderValue("customHeader") ?? "";
          } else {
            /**
             * This is possible if the function is triggered from the user management dashboard
             *
             * In this case set a reasonable default value to use
             */
            customHeaderValue = "default";
          }

          // Perform custom logic based on the value of customHeaderValue

          return oI.revokeSession(input);
        },
      };
    },
  },
});
```
</Tab>
<Tab title="Go" value="go">
```go
import (
    "github.com/supertokens/supertokens-golang/recipe/session"
    "github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
    "github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
    session.Init(&sessmodels.TypeInput{
        Override: &sessmodels.OverrideStruct{
            Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
                originalRevokeSession := *originalImplementation.RevokeSession

                *originalImplementation.RevokeSession = func(sessionHandle string, userContext supertokens.UserContext) (bool, error) {
                    customHeadervalue := ""
                    request := supertokens.GetRequestFromUserContext(userContext)

                    if request != nil {
                        customHeadervalue = request.Header.Get("customHeader")
                    } else {
                        /**
                        * This is possible if the function is triggered from the user management dashboard
                        * 
                        * In this case set a reasonable default value to use
                        */
                        customHeadervalue = "default";
                    }

                    print(customHeadervalue)

                    // Perform custom logic based on the value of customHeadervalue

                    return originalRevokeSession(sessionHandle, userContext)
                }

                return originalImplementation
            },
        },
    })
}
```
</Tab>
<Tab title="Python" value="python">
```python
from typing import Any, Dict

from supertokens_python import get_request_from_user_context
from supertokens_python.recipe import session
from supertokens_python.recipe.session.interfaces import RecipeInterface


def override_session_functions(original_implementation: RecipeInterface):
    original_revoke_session = original_implementation.revoke_session

    async def revoke_session(session_handle: str, user_context: Dict[str, Any]):
        request=get_request_from_user_context(user_context)
        customHeaderValue=""

        if request is not None:
            customHeaderValue=request.get_header("customHeader")
        else:
            #
            # This is possible if the function is triggered from the user management dashboard
            #
            # In this case set a reasonable default value to use
            #
            customHeaderValue="default"

        print(customHeaderValue)
        # Perform custom logic based on the value of customHeadervalue

        return await original_revoke_session(session_handle, user_context)

    original_implementation.revoke_session = revoke_session
    return original_implementation


session.init(
    override=session.InputOverrideConfig(
        functions=override_session_functions,
    ),
)
```
</Tab>
</CodeGroup>
