> ## Documentation Index
> Fetch the complete documentation index at: https://docs-dev-docs-event-stream-action-templates.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Learn how to migrate from a single custom domain to multiple custom domains in Auth0.

# Migrate to Multiple Custom Domains

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

export const AuthCodeBlock = ({filename, icon, language, highlight, children}) => {
  const [displayText, setDisplayText] = useState(children);
  const [copyText, setCopyText] = useState(children);
  const wrapperRef = React.useRef(null);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      if (!window.autorun || !window.rootStore) {
        return;
      }
      unsubscribe = window.autorun(() => {
        let processedChildrenForDisplay = children;
        let processedChildrenForCopy = children;
        for (const [key, value] of window.rootStore.variableStore.values.entries()) {
          const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
          let displayValue = value;
          if (key === "{yourClientSecret}" && value !== "{yourClientSecret}") {
            displayValue = value.substring(0, 3) + "*****MASKED*****";
          }
          processedChildrenForDisplay = processedChildrenForDisplay.replaceAll(new RegExp(escapedKey, "g"), displayValue);
          processedChildrenForCopy = processedChildrenForCopy.replaceAll(new RegExp(escapedKey, "g"), value);
        }
        setDisplayText(processedChildrenForDisplay);
        setCopyText(processedChildrenForCopy);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  useEffect(() => {
    if (!wrapperRef.current) return;
    const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
    let isOverriding = false;
    const handleClick = e => {
      const button = e.target.closest('[data-testid="copy-code-button"]');
      if (!button || !wrapperRef.current.contains(button)) return;
      isOverriding = true;
      navigator.clipboard.writeText = text => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
          return originalWriteText(copyText);
        }
        return originalWriteText(text);
      };
      setTimeout(() => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
        }
      }, 100);
    };
    const wrapper = wrapperRef.current;
    wrapper.addEventListener('click', handleClick, true);
    return () => {
      wrapper.removeEventListener('click', handleClick, true);
      if (navigator.clipboard.writeText !== originalWriteText) {
        navigator.clipboard.writeText = originalWriteText;
      }
    };
  }, [copyText]);
  return <div ref={wrapperRef}>
      <CodeBlock filename={filename} icon={icon} language={language} lines highlight={highlight}>
        {displayText}
      </CodeBlock>
    </div>;
};

This guide helps you migrate from a single custom domain configuration to [multiple custom domains](/docs/customize/custom-domains/multiple-custom-domains). Whether you're adding domains for different brands, regions, or customer segments, this guide provides step-by-step instructions for a smooth transition.

## Migration scenarios

Choose the scenario that best matches your situation:

| Scenario                            | Description                                                                                                         | Use case                                                   | Complexity     | Downtime                     |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | -------------- | ---------------------------- |
| **Adding additional domains**       | You currently have one custom domain and want to add more while keeping the existing domain operational             | Expanding to serve multiple brands or regions              | Low            | None                         |
| **Replacing an existing domain**    | You want to replace your current custom domain with a new one                                                       | Rebranding or domain ownership change                      | Medium         | Minimal (during DNS cutover) |
| **Migrating from canonical domain** | You're currently using your Auth0 canonical domain (e.g., `tenant.auth0.com`) and want to migrate to custom domains | Initial custom domain implementation with multiple domains | Medium to High | None (parallel operation)    |

## Pre-migration checklist

Before starting your migration, ensure you have completed the following:

* Verified domain ownership for all new custom domains
* Reviewed current authentication processes and API integrations
* Identified all applications using the current domain
* Documented current email templates and links
* Obtained SSL/TLS certificates (if using self-managed certificates)
* Tested the new custom domain configuration in a development or staging environment
* Prepared rollback plan
* Scheduled migration during low-traffic period (if applicable)
* Notified stakeholders and users (if required)

## Migration steps

### Add your new custom domains

Add your new custom domains using the [Auth0 Dashboard](/docs/customize/custom-domains/multiple-custom-domains#configure-multiple-custom-domains) or [Management API](https://auth0.com/docs/api/management/v2).

<AuthCodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url 'https://{yourDomain}/api/v2/custom-domains' \
    --header 'authorization: Bearer {yourMgmtApiAccessToken}' \
    --header 'content-type: application/json' \
    --data '{
      "domain": "new-domain.example.com",
      "type": "auth0_managed_certs",
      "domain_metadata": {
        "purpose": "new-brand"
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const newDomain = await management.customDomains.create({
    domain: 'new-domain.example.com',
    type: 'auth0_managed_certs',
    domain_metadata: {
      purpose: 'new-brand'
    }
  });

  console.log('Domain added:', newDomain.custom_domain_id);
  ```
</AuthCodeGroup>

### Verify domain ownership

Complete the domain verification process for each new custom domain:

#### For Auth0-managed certificates

1. Note the CNAME record provided by Auth0
2. Add the CNAME record to your DNS provider
3. Verify the domain through the Dashboard or API

```bash theme={null}
# Get verification details
curl --request GET \
  --url 'https://{yourDomain}/api/v2/custom-domains/{customDomainId}' \
  --header 'authorization: Bearer {yourMgmtApiAccessToken}'

# Verify after adding DNS record
curl --request POST \
  --url 'https://{yourDomain}/api/v2/custom-domains/{customDomainId}/verify' \
  --header 'authorization: Bearer {yourMgmtApiAccessToken}'
```

#### For self-managed certificates

1. Add the required TXT record to your DNS
2. Configure your reverse proxy or CDN
3. Upload your SSL certificate
4. Verify the domain

### Configure default domain (optional)

If you want one domain to be used by default for emails and API calls, [set it as the default domain](/docs/customize/custom-domains/multiple-custom-domains/default-domain):

```bash theme={null}
curl --request PATCH \
  --url 'https://{yourDomain}/api/v2/custom-domains/{customDomainId}' \
  --header 'authorization: Bearer {yourMgmtApiAccessToken}' \
  --header 'content-type: application/json' \
  --data '{
    "is_default": true
  }'
```

### Update application configurations

Update your applications to use the appropriate custom domain:

#### SDK configuration

Update your Auth0 SDK initialization:

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  The only change required is updating the `domain` parameter from your Auth0 canonical domain to your new custom domain.
</Callout>

<AuthCodeGroup>
  ```javascript Auth0 SPA SDK theme={null}
  // Before
  const auth0 = await createAuth0Client({
    domain: 'tenant.auth0.com',
    client_id: '{yourClientId}'
  });

  // After
  const auth0 = await createAuth0Client({
    domain: 'new-domain.example.com', // Use your new custom domain
    client_id: '{yourClientId}'
  });
  ```

  ```javascript Auth0.js theme={null}
  // Before
  const webAuth = new auth0.WebAuth({
    domain: 'tenant.auth0.com',
    clientID: '{yourClientId}'
  });

  // After
  const webAuth = new auth0.WebAuth({
    domain: 'new-domain.example.com',
    clientID: '{yourClientId}'
  });
  ```

  ```javascript Node.js theme={null}
  import { ManagementClient } from "auth0";

  // Before
  const auth0 = new ManagementClient({
    domain: 'tenant.auth0.com',
    clientId: '{yourClientId}',
    clientSecret: '{yourClientSecret}',
  });

  // After
  const auth0 = new ManagementClient({
    domain: 'new-domain.example.com',
    clientId: '{yourClientId}',
    clientSecret: '{yourClientSecret}',
  });
  ```
</AuthCodeGroup>

#### Callback URLs

Update your application's callback URLs in the Auth0 Dashboard:

1. Navigate to [**Auth0 Dashboard** > **Applications**](https://manage.auth0.com/#/applications). Choose the application to configure and select the **Settings** tab.
2. Update **Allowed Callback URLs** to include the new domain:
   ```
   https://new-domain.example.com/callback
   ```
3. Update **Allowed Logout URLs**:
   ```
   https://new-domain.example.com/logout
   ```
4. Update **Allowed Web Origins**:
   ```
   https://new-domain.example.com
   ```

### Update email templates

To make custom domain information available in your email templates:

1. Navigate to **Branding** > **Custom Domains**
2. Set your desired domain as the default
3. Customize your email templates to use the custom domain information in the "From" address, subject, and body as needed

<Note>
  Setting a domain as default doesn't automatically change your emails. It makes the default domain's context available in email templates. You must customize your templates to use that information. The default domain's context will be available when no specific domain is provided through the `auth0-custom-domain` header.
</Note>

### Update social identity providers (IdP)

Update redirect URIs for your social identity providers:

#### Google

1. Go to [Google Cloud Console](https://console.cloud.google.com)
2. Navigate to **APIs & Services** > **Credentials**
3. Add `https://new-domain.example.com/login/callback` to **Authorized redirect URIs**

#### Facebook

1. Go to [Facebook Developers](https://developers.facebook.com)
2. Navigate to your app > **Facebook Login** > **Settings**
3. Add `https://new-domain.example.com/login/callback` to **Valid OAuth Redirect URIs**

#### Other providers

Update redirect URIs for all configured social providers following each provider's documentation.

### Update enterprise connections (if applicable)

If you use SAML, WS-Fed, Azure AD, or other enterprise connections, update their configuration:

#### SAML connections

Update the Assertion Consumer Service (ACS) URL:

```
https://new-domain.example.com/login/callback?connection={connectionName}
```

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  You do not need to manually update the ACS URL with your IdP if you use SP-initiated SAML requests and your IdP accepts dynamic ACS.
</Callout>

#### Azure AD connections

1. Go to Azure Active Directory > **App registrations**
2. Select your app > **Authentication**
3. Add `https://new-domain.example.com/login/callback` to **Redirect URIs**

#### ADFS connections

Update the endpoint in your ADFS settings to use the new custom domain.

### Test authentication

Before completing the migration, thoroughly test:

1. **User login**: Verify users can authenticate through the new domain
2. **Password reset**: Test password reset emails use the correct domain
3. **Email verification**: Verify email verification links work
4. **Social logins**: Test each configured social provider
5. **API calls**: Verify API calls work with the new domain
6. **Token validation**: Ensure JWTs have the correct `iss` claim

### Monitor and verify

After migration:

1. Monitor authentication logs for any errors
2. Check email delivery and link functionality
3. Verify SSL certificate validity and expiration dates
4. Test from different geographic regions (if applicable)
5. Confirm all applications are using the intended custom domains

### Decommission old domain (if applicable)

If you're replacing an existing custom domain:

1. Ensure all applications have migrated to the new domain
2. Monitor traffic to the old domain to confirm it's no longer in use
3. Consider keeping the old domain active for a grace period
4. Delete the old custom domain when ready:

```bash theme={null}
curl --request DELETE \
  --url 'https://{yourDomain}/api/v2/custom-domains/{oldCustomDomainId}' \
  --header 'authorization: Bearer {yourMgmtApiAccessToken}'
```

## Migration patterns

<AccordionGroup>
  <Accordion title="Parallel operation (zero downtime)">
    Run both old and new custom domains simultaneously:

    1. Add new custom domain
    2. Update new applications to use new domain
    3. Keep existing applications on old domain
    4. Gradually migrate applications
    5. Decommission old domain when fully migrated

    **Advantages**: Zero downtime, gradual rollout, easy rollback

    **Disadvantages**: Temporary management overhead
  </Accordion>

  <Accordion title="Phased migration by application">
    Migrate applications one at a time:

    1. Identify applications by priority or risk
    2. Migrate low-risk applications first
    3. Monitor for issues before proceeding
    4. Migrate remaining applications
    5. Clean up old configurations

    **Advantages**: Controlled rollout, early issue detection

    **Disadvantages**: Longer migration timeline
  </Accordion>

  <Accordion title="Blue-green deployment">
    Use separate environments for testing:

    1. Set up new custom domain in staging environment
    2. Test all functionality thoroughly
    3. Cut over production in one operation
    4. Keep old domain as backup
    5. Decommission after verification period

    **Advantages**: Thorough testing, quick rollback

    **Disadvantages**: Requires separate environment
  </Accordion>
</AccordionGroup>

## Handling existing user sessions

When migrating custom domains, existing user sessions may be affected:

### Session considerations

* Sessions created with the old domain remain valid until expiration
* New logins will create sessions with the new domain
* Cross-domain sessions require careful planning

### Recommended approach

1. **Notify users**: Inform users they may need to log in again
2. **Grace period**: Keep old domain active during transition
3. **Session transfer**: Use Universal Login to handle session migration
4. **Clear guidance**: Provide clear instructions if users experience issues

## Rollback plan

If you encounter issues during migration:

### Immediate rollback

1. Revert application configurations to use the old domain
2. Keep the new custom domain configured for future attempts
3. Document the issue for troubleshooting

### Partial rollback

1. Identify affected applications
2. Revert only those applications to the old domain
3. Investigate and fix issues
4. Re-migrate when ready

### Complete rollback

1. Update all application configurations to use the old domain
2. If you set a default domain, change it back to the old domain
3. Notify users of any required actions
4. Schedule another migration attempt

## Troubleshooting

### Common issues and solutions

| Issue                         | Cause                                  | Solution                                                                           |
| ----------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------- |
| Domain verification fails     | DNS records not propagated             | Wait for DNS propagation (can take up to 48 hours), verify CNAME record is correct |
| Login redirects to old domain | Application configuration not updated  | Update SDK initialization and callback URLs                                        |
| Email links use wrong domain  | Default domain not configured          | Set default domain or configure email routing                                      |
| Social login fails            | Redirect URIs not updated              | Add new custom domain to social provider's allowed redirect URIs                   |
| Invalid token issuer          | JWT validation expects old domain      | Update token validation to accept new domain as issuer                             |
| SAML assertion errors         | ACS URL not updated                    | Update SAML configuration with new custom domain URL                               |
| Certificate errors            | Certificate not provisioned or expired | Verify domain, wait for certificate provisioning, or update certificate            |

### Getting help

If you encounter issues during migration:

1. Check [Auth0 Community](https://community.auth0.com/) for similar issues
2. Review [troubleshooting documentation](https://support.auth0.com/center/s/knowledge?selectedTopics=Custom%20Domains\&isTopicFilter=true)
3. Contact [Auth0 Support](https://support.auth0.com/) with:
   * Your tenant name
   * Custom domain IDs
   * Detailed description of the issue
   * Steps to reproduce
   * Error messages or logs

## Post-migration best practices

After completing your migration:

1. **Document the configuration**: Record which applications use which custom domains
2. **Monitor certificate expiration**: Set up alerts for certificate renewal
3. **Review periodically**: Ensure custom domain configuration aligns with business needs
4. **Update runbooks**: Update operational documentation with new custom domain information
5. **Train team members**: Ensure your team understands the new multi-domain setup
6. **Plan for scale**: Consider how you'll manage additional domains in the future

## Learn more

* [Multiple Custom Domains](/docs/customize/custom-domains/multiple-custom-domains)
* [Default Custom Domain](/docs/customize/custom-domains/multiple-custom-domains/default-domain)
* [Management API for Multiple Custom Domains](https://auth0.com/docs/api/management/v2)
* [Configure Features to Use Custom Domains](/docs/customize/custom-domains/configure-features-to-use-custom-domains)
