Skip to content
Esc
navigateopen⌘Jpreview
Dashboard
On this page

Customize the Magic Link

See how to change the magic link or how to generate it manually

Override the email delivery backend function

You can change the URL of Magic Links by providing overriding the email delivery configuration on the backend.

import SuperTokens from "supertokens-node";
import Passwordless from "supertokens-node/recipe/passwordless";
import Session from "supertokens-node/recipe/session";

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  recipeList: [
    Passwordless.init({
      contactMethod: "EMAIL", // This example will work with any contactMethod
      // This example works with the "USER_INPUT_CODE_AND_MAGIC_LINK" and "MAGIC_LINK" flows.
      flowType: "USER_INPUT_CODE_AND_MAGIC_LINK",

      emailDelivery: {
        override: (originalImplementation) => {
          return {
            ...originalImplementation,
            sendEmail: async function (input) {
              return originalImplementation.sendEmail({
                ...input,
                urlWithLinkCode: input.urlWithLinkCode?.replace(
                  // This is: `<YOUR_WEBSITE_DOMAIN>/auth/verify`
                  "http://localhost:3000/auth/verify",
                  "http://your.domain.com/your/path",
                ),
              });
            },
          };
        },
      },
    }),
    Session.init({
      /* ... */
    }),
  ],
});
import (
	"strings"

	"github.com/supertokens/supertokens-golang/ingredients/emaildelivery"
	"github.com/supertokens/supertokens-golang/recipe/passwordless"
	"github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels"
	"github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
	supertokens.Init(supertokens.TypeInput{
		RecipeList: []supertokens.Recipe{
			passwordless.Init(plessmodels.TypeInput{
				EmailDelivery: &emaildelivery.TypeInput{
					Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface {
						ogSendEmail := *originalImplementation.SendEmail
						(*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error {
							// By default: `<YOUR_WEBSITE_DOMAIN>//auth/verify`
							newUrl := strings.Replace(
								*input.PasswordlessLogin.UrlWithLinkCode,
								"http://localhost:3000/auth/verify",
								"http://localhost:3000/custom/path",
								1,
							)
							input.PasswordlessLogin.UrlWithLinkCode = &newUrl
							return ogSendEmail(input, userContext)
						}
						return originalImplementation
					},
				},
			}),
		},
	})
}
from supertokens_python import init, InputAppInfo
from supertokens_python.recipe.passwordless.types import EmailDeliveryOverrideInput, EmailTemplateVars
from supertokens_python.recipe import passwordless
from typing import Dict, Any
from supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig

def custom_email_deliver(original_implementation: EmailDeliveryOverrideInput) -> EmailDeliveryOverrideInput:
    original_send_email = original_implementation.send_email

    async def send_email(template_vars: EmailTemplateVars, user_context: Dict[str, Any]) -> None:
        assert template_vars.url_with_link_code is not None
        # By default: `<YOUR_WEBSITE_DOMAIN>//auth/verify`
        template_vars.url_with_link_code = template_vars.url_with_link_code.replace(
            "http://localhost:3000/auth/verify", "http://localhost:3000/custom/path")
        return await original_send_email(template_vars, user_context)

    original_implementation.send_email = send_email
    return original_implementation

init(
    app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
    framework='...',
    recipe_list=[
        passwordless.init(
            email_delivery=EmailDeliveryConfig(override=custom_email_deliver)
        )
    ]
)

Change the frontend page

UI type

When the user clicks the magic link, you need to render the LinkClicked component that exported by the SDK on that page. By default, this already happens on the <YOUR_WEBSITE_DOMAIN>/auth/verify path. To change this, you need to:

When the user clicks the magic link, you need to build your own UI on that page to handle the link clicked. You also need to disable the pre-built UI provided by the SDK for the link clicked screen as shown below:

import Passwordless from "supertokens-auth-react/recipe/passwordless";

Passwordless.init({
  contactMethod: "EMAIL", // This example will work with any contactMethod
  linkClickedScreenFeature: {
    disableDefaultUI: true,
  },
});
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)

supertokensUIPasswordless.init({
  contactMethod: "EMAIL", // This example will work with any contactMethod
  linkClickedScreenFeature: {
    disableDefaultUI: true,
  },
});

You can use the backend SDK to generate magic links as shown below:

import Passwordless from "supertokens-node/recipe/passwordless";

async function createMagicLink(email: string) {
  const magicLink = await Passwordless.createMagicLink({ email, tenantId: "public" });

  console.log(magicLink);
}
import (
	"fmt"

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

func main() {
	email := "..."

	tenantId := "public"
	magicLink, err := passwordless.CreateMagicLinkByEmail(tenantId, email)
	if err != nil {
		// handle error
	}

	fmt.Println(magicLink)
}
from supertokens_python.recipe.passwordless.asyncio import create_magic_link

async def create_link(email: str):
    magic_link = await create_magic_link("public", email, phone_number=None)

    print(magic_link)
from supertokens_python.recipe.passwordless.syncio import create_magic_link

def create_link(email: str):
    magic_link = create_magic_link("public", email, phone_number=None)

    print(magic_link)

You can change how long a user can use an OTP or a Magic Link to log in by changing the passwordless_code_lifetime core configuration value. You configure this value in milliseconds and it defaults to 900000 (15 minutes).

  • Open the SaaS Dashboard, select the relevant Managed deployment, and open Configuration.
  • In the Passwordless configuration card, change the value. Configuration changes are saved automatically.
docker run \
  -p 3567:3567 \
  -e PASSWORDLESS_CODE_LIFETIME=60000 \
  -d supertokens/supertokens-<db name>
# You need to add the following to the config.yaml file.
# The file path can be found by running the "supertokens --help" command

passwordless_code_lifetime: 60000
passwordless_code_lifetime: 60000

See also

API reference

API schema and response details