Skip to main content

Documentation Index

Fetch the complete documentation index at: https://auth0-feat-experiment-center.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

When an experiment is active, the Experiment Center injects the ExperimentContext object into supported Action triggers.

Supported triggers

Experiment context is available for Auth0 Actions with the following:

The event.experimentation object

In supported triggers, the Experiment Center adds an experimentation field to the event object:
interface ExperimentContext {
  experiment_id: string;   // The active experiment ID
  variation_id: string;    // The assigned variation ID
  config: {                // Merged configuration: baseline + overrides
    [paramName: string]: { value: unknown };
  };
  is_control: boolean;     // True when this is the control variation
}
event.experimentation is null (not undefined) when no experiment is active. Always check before accessing properties.

config contains the full merged configuration

The config object contains every parameter defined on the feature flag, merged with the assigned variation’s overrides. You never need to look up baseline values or write fallback logic. If the parameter exists on the feature flag, it exists in config.

Null-safety pattern

Check for an active experiment before reading any properties:
exports.onExecutePostLogin = async (event, api) => {
  const ec = event.experimentation;
  if (!ec) return; // No active experiment; nothing to do

  // Safe to access ec.config, ec.variation_id, ec.is_control here
};
Use this pattern in all three supported triggers. The early return keeps your Action clean and ensures it behaves correctly during periods when no experiment is running.

Example: post_login — conditional MFA policy

This example reads a boolean parameter and applies a different MFA policy depending on the variation.
// post_login Action
exports.onExecutePostLogin = async (event, api) => {
  const ec = event.experimentation;
  if (!ec) return;

  const enforceMfa = ec.config?.require_mfa?.value;

  if (enforceMfa === true) {
    // Treatment variation: enforce MFA for this user
    api.multifactor.enable("any", { allowRememberBrowser: false });
  }
  // Control variation: no MFA enforcement change (baseline behavior)
};
ec.config.require_mfa.value is true for users in the treatment variation and false (the baseline) for users in the control variation. No fallback logic needed.

Example: post_login — set a custom claim based on variation

This example stamps the experiment assignment into the user’s ID token as a custom claim. Some analytics pipelines read token claims instead of tenant logs.
// post_login Action
exports.onExecutePostLogin = async (event, api) => {
  const ec = event.experimentation;
  if (!ec) return;

  // Add experiment assignment to the ID token
  api.idToken.setCustomClaim("https://example.com/experiment", {
    experiment_id: ec.experiment_id,
    variation_id: ec.variation_id,
  });
};
Use a namespaced claim URL per the OIDC custom claim convention. The recipient (your app) can then read the claim from the token without querying tenant logs.

Example: pre_user_registration — variation-based metadata

This example uses a registration-flow experiment to set user_metadata based on which variant the registering user lands in.
// pre_user_registration Action
exports.onExecutePreUserRegistration = async (event, api) => {
  const ec = event.experimentation;
  if (!ec) return;

  const onboardingVariant = ec.config?.onboarding_variant?.value;

  if (onboardingVariant) {
    api.user.setUserMetadata("onboarding_variant", onboardingVariant);
    api.user.setUserMetadata("onboarding_experiment_id", ec.experiment_id);
  }
};
The metadata is written during registration, so it follows the user across sessions. You can query it later to understand which signup cohort a user belongs to.

Example: post_user_registration — trigger downstream enrollment

This example fires a webhook after registration based on the variation assigned to the new user.
// post_user_registration Action
exports.onExecutePostUserRegistration = async (event, api) => {
  const ec = event.experimentation;
  if (!ec) return;

  const enrollInNewProgram = ec.config?.enroll_welcome_program?.value;

  if (enrollInNewProgram === true) {
    await fetch("https://your-backend.example.com/api/enrollment", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        user_id: event.user.user_id,
        email: event.user.email,
        experiment_id: ec.experiment_id,
        variation_id: ec.variation_id,
      }),
    });
  }
};
Only users in the treatment variation trigger the enrollment webhook. Control users follow the standard post-registration path.

Use the is_control parameter

is_control is true when the user is in the control group. Use it when you need to take an explicit action for control users, or to avoid running treatment-specific code on the control group.
exports.onExecutePostLogin = async (event, api) => {
  const ec = event.experimentation;
  if (!ec) return;

  if (!ec.is_control) {
    // Only track treatment users in your analytics system
    // (control users are tracked separately, outside the Action)
    await logTreatmentEvent(ec.experiment_id, ec.variation_id, event.user.user_id);
  }
};
For branching on behavior (not analytics), prefer checking config parameter values directly. They are more explicit and readable.

Read tenant logs

You do not need to write any code to get experiment metadata into your tenant logs. The Experiment Center enriches auth events automatically, regardless of what your Actions do. The event.experimentation object in Actions gives you access to the same context for branching logic; it is not the source of tenant log enrichment.