---
title: Customize the Magic Link
description: See how to change the magic link or how to generate it manually
sidebar:
  order: 30
---

## Change the magic link URL

### Override the email delivery backend function

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

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```tsx
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({
      /* ... */
    }),
  ],
});
```
</Tab>
<Tab title="Go" value="go">
```go
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
					},
				},
			}),
		},
	})
}
```
</Tab>
<Tab title="Python" value="python">
```python check=false reason="This example omits surrounding application and SuperTokens configuration."
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)
        )
    ]
)
```
</Tab>
</CodeGroup>


### Change the frontend page

<UITypeSwitch />

<VariantContent storageKey="ui-type" value="prebuilt">

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Reactjs" value="reactjs">
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:

#### 1. Disable the default UI for the link clicked screen:
</ContentOption>
<ContentOption title="Angular" value="angular">
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:
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx
import Passwordless from "supertokens-auth-react/recipe/passwordless";

Passwordless.init({
  contactMethod: "EMAIL", // This example will work with any contactMethod
  linkClickedScreenFeature: {
    disableDefaultUI: true,
  },
});
```
</Tab>
<Tab title="Angular" value="angular">
```tsx check=false reason="This example omits surrounding application and SuperTokens configuration."
// 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,
  },
});
```
</Tab>
</CodeGroup>

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Reactjs" value="reactjs">
#### 2. Render the link clicked screen on your custom route:
</ContentOption>
</DependentContent>

<CodeGroup passive group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx
import React from "react";
import { LinkClicked } from "supertokens-auth-react/recipe/passwordless/prebuiltui";
function CustomLinkClickedScreen() {
  return <LinkClicked />;
}
```
</Tab>
</CodeGroup>

</VariantContent>
<VariantContent storageKey="ui-type" value="custom">

:::info[Caution]
Not applicable since you do not use the pre-built UI
:::

</VariantContent>

---

## Generate the link manually


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

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

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

  console.log(magicLink);
}
```
</Tab>
<Tab title="Go" value="go">
```go
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)
}
```
</Tab>
<Tab title="Python" value="python">
<DependentContent group="python-io-style" label="I/O style">
<ContentOption title="Asyncio" value="asyncio">
```python
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)
```
</ContentOption>
<ContentOption title="Syncio" value="syncio">
```python
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)
```
</ContentOption>
</DependentContent>
</Tab>
</CodeGroup>

:::info[Multi Tenancy]

Notice that you pass the `"public"` `tenantId` to the function call above - which is the default `tenantId`.

If you are using the multi tenancy feature, you can pass in another `tenantId` which SuperTokens embeds in the link. This ensures that when the user clicks on the link and signs up, they sign up to the tenant you want to give them access to.

Note that the generated link uses the configured `websiteDomain` from the `appInfo` object (in `supertokens.init`), however, you can change the domain of the generated link to match that of the tenant ID.

:::

---

## Change the link lifetime

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).

:::warning[Each new OTP / magic link generated, either by opening a new browser or by clicking on the "Resend" button, has a lifetime according to the `passwordless_code_lifetime` setting.]

:::

<DependentContent passive group="core-deployment">
<ContentOption title="With Saas" value="saas">
- Open the [SaaS Dashboard](https://supertokens.com/dashboard), select the relevant **Managed** deployment, and open **Configuration**.
- In the **Passwordless** configuration card, change the value. Configuration changes are saved automatically.
</ContentOption>
</DependentContent>

<CodeGroup group="core-deployment">
<Tab title="With Docker" value="with-docker">
```bash
docker run \
  -p 3567:3567 \
  -e PASSWORDLESS_CODE_LIFETIME=60000 \
  -d supertokens/supertokens-<db name>
```
</Tab>
<Tab title="Without Docker" value="without-docker">
```yaml
# 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
```
</Tab>
<Tab title="With Saas" value="saas">
```yaml
passwordless_code_lifetime: 60000
```
</Tab>
</CodeGroup>

---

## See also

<CardGroup cols={3}>
  <Card title="Customize the OTP" href="/authentication/passwordless/customize-the-otp" />
  <Card title="Hooks and overrides" href="/authentication/passwordless/hooks-and-overrides" />
  <Card title="Email and SMS behavior" href="/authentication/passwordless/configure-email-and-sms-behavior" />
  <Card title="Invite link sign up" href="/authentication/passwordless/invite-link-flow" />
  <Card title="Allow list sign up" href="/authentication/passwordless/allow-list-flow" />
  <Card title="Email Delivery" href="/platform-configuration/email-delivery" />
  <Card title="SMS Delivery" href="/platform-configuration/sms-delivery" />
</CardGroup>
