> ## 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 your existing Auth0 Rules code to Auth0 Actions code.

# Migrate from Rules to Actions

When converting existing Rules to Actions, you should associate the new Action with the Post-Login (`post-login`) Trigger of the Login Flow. If you follow the steps below and keep your Actions in the same order as your original Rules, the functionality should be identical.

## Plan your migration

Post-Login Actions run after existing Rules, so you can either convert Rules one at a time in the Dashboard or all at once using the <Tooltip tip="Management API: A product to allow customers to perform administrative tasks." cta="View Glossary" href="/docs/glossary?term=Management+API">Management API</Tooltip>.

You will need to convert code, and then activate the Action and deactivate the Rule. Activating the Action and deactivating the Rule can be done quickly in succession, but depending on the order, there might be a short period of time where either both or neither are running.

Because of this, we recommend migrating your pipeline step by step: convert pieces of your Rules code to Action code, test in a staging environment, then go live with one piece at a time. Because active Rules run before deployed Actions, if you start at the end of your Rules pipeline and work backwards, you can keep some logic in Rules as you build and test other logic in Actions.

<Card title="Tips when planning your migration">
  * Keep your Actions and Rules 1:1, so functionality can be turned off and on in blocks and tested.
  * Use flags in user metadata to avoid duplicating expensive or one-time operations.
  * Start at the end of your Rules pipeline and work backwards; because active Rules run before deployed Actions, you can keep some logic in Rules as you build and test other logic in Actions.
  * Make sure to run changes at a time when impact and traffic will be lowest.
  * Consider temporarily [customizing your login page](/docs/customize) to halt logins if the cutover could cause invalid logins or gaps in protection.
  * Consider using the [Auth0 Deploy CLI](/docs/deploy-monitor/deploy-cli-tool) to script, test, and quickly implement the migration all at once or iteratively.
</Card>

## Understand limitations

While Actions can handle the vast majority of things that Rules can, you should be aware of a few limitations before you start your migration. (Remember: you can have both Rules and Actions running as you migrate.)

* Actions are not provided with [an access token for the Management API](/docs/customize/rules/use-management-api) or access to the global `auth0` object as in Rules. To learn how Management API calls can still be made, read the [Convert Code](#convert-code) section.

For the full list of limitations, see [Actions Limitations](/docs/customize/actions/limitations).

## Convert code

To convert a Rule to an Action, you must replace Rule-specific code with Actions code. This section covers the tasks you will need to perform to turn a functioning Rule into its equivalent Action.

<Card title="Tips when converting code">
  * In general, look for the read-only properties of Rules `user` and `context` objects on the Actions `event` object. Look for any side effects your Actions have on the system (like failing a login or updating user metadata) in the `api` object functions.
  * Use the Actions Code Editor in the Auth0 Dashboard to write your code; it will help by highlighting errors and supplying auto-complete suggestions.
  * Before you go live, thoroughly [test your new Actions](/docs/customize/actions/test-actions) in a [staging or test environment](/docs/get-started/auth0-overview/create-tenants/set-up-multiple-environments).
</Card>

### Copy Rule code to a new Action

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  We recommend copying your Rule code into a new Action and using the Actions Code Editor in the Auth0 Dashboard; it will help you identify outstanding issues with your code.
</Callout>

1. Log in to your production tenant, and copy the code from the Rule you want to convert.
2. Switch to a non-production tenant, and navigate to [Auth0 Dashboard > Actions > Library](https://manage.auth0.com/#/select-tenant?path=/actions/library).
3. Select **Build Custom**, then:

   * Enter a **Name** for your Action that matches the name of the Rule you're converting.
   * Locate **Trigger**, and select **Login / Post Login**.
   * Locate **Runtime**, and select **Node 16.**
   * Select **Create**.
4. In the code block of the Actions Code Editor, paste the Rule code you want to convert below the exported `onExecutePostLogin` function.
5. Make the changes detailed in the rest of this article as you move the code into the function.

### Change the function declaration

Rules use a plain, declared function with `user`, `context`, and `callback` parameters, while Actions use a function exported to a specific name. Make the following change; for now, ignore any errors that appear.

**Before**

```js lines theme={null}
async function myRulesFunction(user, context, callback) {
    // ... additional code
}
```

**After**

```js lines theme={null}
exports.onExecutePostLogin = async (event, api) => {
	// ... additional code
};
```

### Change how user data is accessed

In Rules, data about the user logging in is stored in the [`user` object](/docs/customize/rules/user-object-in-rules). In Actions, this data is found in the `user` property of the [`event` object](/docs/customize/actions/explore-triggers/signup-and-login-triggers/login-trigger/post-login-event-object). The majority of existing properties are accessible in this new location.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Data stored or modified in properties in the `event` object are not accessible in other Actions.
</Callout>

**Before**

```javascript lines theme={null}
function myRulesFunction(user, context, callback) {
	const userEmail = user.email;
	const userId = user.user_id;

	// This property could be undefined in Rules.
	const userAppMetadata = user.app_metadata || {};

	// ... additional code
}
```

**After**

```js lines theme={null}
exports.onExecutePostLogin = async (event, api) => {
	const userEmail = event.user.email;
	const userId = event.user.user_id;

	// This property will never be undefined in Actions.
	const userAppMetadata = event.user.app_metadata;

	// ... additional code
};
```

### Change how context data is accessed

In Rules, data about the current login session is stored in the [`context` object](/docs/customize/rules/context-object). For Actions, this data has been reshaped and moved to the [`event` object](/docs/customize/actions/explore-triggers/signup-and-login-triggers/login-trigger/post-login-event-object). Many of the properties moved over as-is, but some have been combined to increase clarity.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Data stored or modified in properties in the `event` object are not accessible in other Actions. If your Rule triggers core functionality by setting data on these properties, like `context.idToken` or `context.multifactor`, please read one of the sections below that addresses your use case.
</Callout>

**Before**

```javascript lines theme={null}
function myRulesFunction(user, context, callback) {
	const clientId = context.clientID;
	const clientMetadata = context.clientMetadata || {};

	const connectionId = context.connectionID;
	const connectionMetadata = context.connectionMetadata || {};

	const protocol = context.protocol;

	const tenant = context.tenant;

	// ... additional code
}
```

**After**

```js lines theme={null}
exports.onExecutePostLogin = async (event, api) => {
	const clientId = event.client.client_id;
	const clientMetadata = event.client.metadata;

	const connectionId = event.connection.id;
	const connectionMetadata = event.connection.metadata;

	const protocol = event.transaction.protocol;

	const tenant = event.tenant.id;

	// ... additional code
};
```

### Convert dependencies

Rules include dependencies in a way that requires including the version number in a `require` statement. Actions use a more standard CommonJS syntax and require that the versions be indicated outside of the code editor.

In Rules, only specific versions of specific packages are allowed, and adding new packages and versions requires a request to Auth0. In Actions, you can require any package that is available in the `npm` Registry.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  If your `npm` modules are not on the latest version, this is a great time to get up to date!
</Callout>

1. Search for `require` statements inside your Rule code.
2. Remove version numbers, but make a note of them.
3. Add the dependency by following the steps in the "Add a Dependency" section of [Write Your First Action](/docs/customize/actions/write-your-first-action) (if the dependency is not a [core NodeJS module](https://github.com/nodejs/node/tree/main/lib); if the dependency is a core NodeJS module, you do not need to include it).
4. Move the found `require` statements outside of the `function` declaration:

**Before**

```javascript lines theme={null}
function myRulesFunction(user, context, callback) {
	const dependency = require("dependency@1.2.3");

	// ... additional code
}
```

**After**

```javascript lines theme={null}
const dependency = require("dependency"); // v1.2.3
exports.onExecutePostLogin = async (event, api) => {
	// ... additional code
};
```

### Convert callbacks

When a Rule is finished processing, it must call the `callback()` function and pass in an error if the login fails. Conversely, Actions can return on success, or call an `api` method with a message if the login fails. All instances of `callback()` in a Rule should be removed or replaced with `api.access.deny()` for failure. In both Rules and Actions, if processing needs to stop for a specific condition, use a `return` statement.

**Before**

```javascript lines theme={null}
function myRulesFunction(user, context, callback) {
	const userAppMetadata = user.app_metadata || {};
	if (userAppMetadata.condition === "success") {
		// This Rule succeeded, proceed with next Rule.
		return callback(null, user, context);
	}

	if (userAppMetadata.condition === "failure") {
		// This Rule failed, stop the login with an error response.
		return callback(new Error("Failure message"));
	}

	// ... additional code
}
```

**After**

```js lines theme={null}
exports.onExecutePostLogin = async (event, api) => {
	if (event.user.app_metadata.condition === "success") {
		// This Action succeeded, proceed with next Action.
		return;
	}

	if (event.user.app_metadata.condition === "failure") {
		// This Action failed, stop the login with an error response.
		return api.access.deny("Failure message");
	}

	// ... additional code
};
```

### Change handling of secrets

In Rules, you set configuration values globally, which means that all Rules can access all secret values. (To learn more, read [Store Rule Configurations](/docs/customize/rules/configuration).) In Actions, you set configuration values for each individual Action. You can't access an Action's secret value from outside the context of the Action.

To convert secrets from Rules to Actions:

1. Save the values needed for the specific Action you are working on.
2. Add a Secret for each value you need to access from inside the Action. To learn how, read the **Add a Secret** section in [Write Your First Action](/docs/customize/actions/write-your-first-action).
3. Convert your code:

**Before**

```javascript lines theme={null}
function myRulesFunction (user, context, callback) {
  const { CLIENT_ID, CLIENT_SECRET } = configuration;

  // ... additional code
}
```

**After**

```js lines theme={null}
exports.onExecutePostLogin = async (event, api) => {
  const { CLIENT_ID, CLIENT_SECRET } = event.secrets;

  // ... additional code
}
```

As with Rules, Auth0 encrypts all secret values at rest.

### Convert custom claims in tokens

Rules and Actions can both add custom claims to ID and <Tooltip tip="Access Token: Authorization credential, in the form of an opaque string or JWT, used to access an API." cta="View Glossary" href="/docs/glossary?term=access+tokens">access tokens</Tooltip>. In Rules, this is a property of the `context` object, while Actions uses a method on the [`api` object](/docs/customize/actions/explore-triggers/signup-and-login-triggers/login-trigger/post-login-api-object).

**Before**

```javascript lines theme={null}
function myRulesFunction(user, context, callback) {
	const userAppMetadata = user.app_metadata || {};
	const namespace = "https://namespace/";

	context.idToken[`${namespace}/emp_id`] = userAppMetadata.emp_id;
	context.accessToken[`${namespace}/emp_id`] = userAppMetadata.emp_id;

	// ... additional code
}
```

**After**

```js lines theme={null}
exports.onExecutePostLogin = async (event, api) => {
	const namespace = "https://namespace/";

	api.idToken.setCustomClaim(
		`${namespace}/emp_id`, 
		event.user.app_metadata.emp_id
	); 		   

	api.accessToken.setCustomClaim(
		`${namespace}/emp_id`, 
		event.user.app_metadata.emp_id
	);

	// ... additional code
};
```

### Convert multi-factor triggering

In Rules, <Tooltip tip="Multi-factor authentication (MFA): User authentication process that uses a factor in addition to username and password such as a code via SMS." cta="View Glossary" href="/docs/glossary?term=multi-factor+authentication">multi-factor authentication</Tooltip> can be triggered by modifying the `multifactor` property of the [`context` object](/docs/customize/rules/context-object). In Actions, this is done with a [method on the `api` object](/docs/customize/actions/explore-triggers/signup-and-login-triggers/login-trigger/post-login-api-object).

**Before**

```javascript lines theme={null}
function myRulesFunction(user, context, callback) {
	if (user.app_metadata.needs_mfa === true) {
		context.multifactor = { 
			provider: "any", 
			allowRememberBrowser: false,
		};
	}

	// ... additional code
}
```

**After**

```js lines theme={null}
exports.onExecutePostLogin = async (event, api) => {
	if (event.user.app_metadata.needs_mfa === true) {
		api.multifactor.enable("any", { allowRememberBrowser: false });
	}

	// ... additional code
};
```

### Convert user metadata updates

Updating the `user_metadata` and `app_metadata` properties in Rules requires a call to the Management API, which can lead to [rate limit](/docs/troubleshoot/customer-support/operational-policies/rate-limit-policy/management-api-endpoint-rate-limits) errors. Actions, however, provides a way to indicate multiple user metadata changes but only call the Management API once.

**Before**

```javascript lines theme={null}
function myRulesFunction(user, context, callback) {
	user.app_metadata = user.app_metadata || {}; 
	user.app_metadata.roles = user.app_metadata.roles || [];
	user.app_metadata.roles.push("administrator"); 

	auth0.users
		.updateAppMetadata(user.user_id, user.app_metadata) 
		.then(() => callback(null, user, context))
		.catch((err) => callback(err));

	// ... additional code
}
```

If subsequent Rules need to update the user metadata, then they would have to call the Management API separately, making it more likely that you would hit the [rate limit](/docs/troubleshoot/customer-support/operational-policies/rate-limit-policy/management-api-endpoint-rate-limits).

**After**

```js lines theme={null}
exports.onExecutePostLogin = async (event, api) => {
	const userRolesUpdated = event.user.app_metadata.roles || [];
	userRolesUpdated.push("administrator"); 

	// Note the two different methods here. 
	api.user.setAppMetadata("roles", userRolesUpdated);
	api.user.setUserMetadata("hasRoles", true);

	// ... additional code
};
```

If subsequent Actions needed to update the user metadata, then they would need to call `api.user.setUserMetadata` or `api.user.setAppMetadata`. In Actions, multiple calls to these functions across one or more Actions will result in a single Management API call once the flow is complete.

### Convert other Management API calls

In general, we do not recommend calling the Management API from a high-traffic, critical path like Rules or Actions. Requests to all Auth0 APIs are [rate limited](/docs/troubleshoot/customer-support/operational-policies/rate-limit-policy), including calls from extensibility points, and calling an API for all logins could easily result in failed logins at high-traffic times.

However, If the calls are necessary and are configured to avoid rate limits, it's possible to call the Management API from within Actions. As mentioned in the "Understand limitations" section earlier in this article, Actions are not provided with an access token for the Management API, so you will need to get an access token before activating your Action:

1. [Register a Machine-to-Machine application and authorize it for the Management API](/docs/get-started/auth0-overview/create-applications/machine-to-machine-apps).
2. Save the **Client ID** and **Client Secret** in the Action.
3. [Get an access token for the Management API](/docs/secure/tokens/access-tokens/management-api-access-tokens/get-management-api-access-tokens-for-production).
4. Call the Management API:

   <Warning>
     Actions cannot persist data across executions, so it's not possible to cache the access token for any length of time; because every Management API call also requires an Authentication API call, calling the Management API is a very expensive operation.
   </Warning>

**Before**

```javascript lines theme={null}
function myRulesFunction(user, context, callback) {
	const ManagementClient = require("auth0@2.9.1").ManagementClient; 
	const managementClientInstance = new ManagementClient({
		// These come from built-in Rules globals
		token: auth0.accessToken, 
		domain: auth0.domain,
	}); 

	managementClientInstance.users.assignRoles(
		{ id: user.user_id }, 
		{ roles: ["ROLE_ID_TO_ADD"] }, 
		(error, user) => {
			if (error) {
				return callback(error);
			}

			// ... additional code
		}
	);
}
```

**After**

```javascript lines theme={null}
const auth0Sdk = require("auth0");
exports.onExecutePostLogin = async (event, api) => {
	const ManagementClient = auth0Sdk.ManagementClient;

	// This will make an Authentication API call
	const managementClientInstance = new ManagementClient({
		// These come from a machine-to-machine application
		domain: event.secrets.M2M_DOMAIN,
		clientId: event.secrets.M2M_CLIENT_ID,
		clientSecret: event.secrets.M2M_CLIENT_SECRET,
		scope: "update:users"
	});

	managementClientInstance.users.assignRoles(
		{ id: event.user.user_id }, 
		{ roles: ["ROLE_ID_TO_ADD"]}, 
		(error, user) => {
			if (error) {
				return api.access.deny(error.message);
			}

			// ... additional code
		}
	);
};
```

### Convert redirects

Rules can redirect a user who is logging in to an external page, then wait for a response. In this case, all Rules before the redirection will run twice--once before the redirect and once on the response. The logic for the redirect and the response are typically contained in the same Rule.

In Actions, the Action pipeline is paused when the redirect happens and picks up once the user returns. Also, the exported redirect triggering function is separate from the redirect callback.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Doing all redirects correctly in Actions is beyond the scope of this guide. For more detailed information, read [Redirect with Actions](/docs/customize/actions/explore-triggers/signup-and-login-triggers/login-trigger/redirect-with-actions).
</Callout>

**Before**

```javascript lines theme={null}
function myRulesFunction(user, context, callback) {
    if (context.protocol === "redirect-callback") {
        // User was redirected to the /continue endpoint
        user.app_metadata.wasRedirected = true;
        return callback(null, user, context);
    } else if (
        context.protocol === "oauth2-password" ||
        context.protocol === "oauth2-refresh-token" ||
        context.protocol === "oauth2-resource-owner"
    ) {
        // User cannot be redirected
        return callback(null, user, context);
    }
    // User is logging in directly
    if (!user.app_metadata.wasRedirected) {
        context.redirect = {
            url: "https://example.com",
        };
        callback(null, user, context);
    }
}
```

**After**

```js lines theme={null}
exports.onExecutePostLogin = async (event, api) => {
    api.accessToken.setClaim("https://dev.TLD/wasRedirected", true)
```

### Convert current SSO clients references

The Rules `context.sso` object provides details about the current session and clients using it. For more information, see the `context.sso` entry in [Context Object Properties in Rules](/docs/customize/rules/context-object). Similar information is available in the Actions `event.session` object.

**Before**

```javascript lines theme={null}
function (user, context, callback) {

  const clients = context.sso?.current_clients ?? []; 

  if (clients.length > 0) { 
	context.idToken.clients = clients.join(" "); 
  }

  return callback(null, user, context);
}
```

**After**

```js lines theme={null}
exports.onExecutePostLogin = async (event, api) => {
  const clients = event?.session?.clients ?? []; 

  if (clients.length > 0) { 
    api.idToken.setCustomClaim('clients', clients.map(c=> c?.client_id).join(" ")); 
  }
};
```

## Complete the migration

Once your new Actions code has been written and tested, you must activate the Action and deactivate the Rule. These two tasks can be done quickly in succession, but depending on the order, there might be a short period of time where either both or neither are running. Because active Rules run before deployed Actions, if you start at the end of your Rules pipeline and work backwards, you can keep some logic in Rules as you build and test other logic in Actions.
