Guide · Auth0

Migrate Auth0 Node.js SDK (auth0) from v3 to v4

Version 4 of the auth0 npm package dropped callbacks, wraps every result in an object with data, and removed the shortcut methods on both clients in favor of namespaces such as management.users and auth.oauth.

node-auth0 4.0.0 was released on September 14, 2023, after ten betas. Its migration guide sorts the changes into three groups (general, Authentication API and Management API), and the general ones touch nearly every call site. 4.x is no longer the latest major: the repository has since published 5.0.0 (September 15, 2025), 6.0.0 and 7.0.0, each with its own migration guide, and the v4 guide is no longer on the main branch. If you are on 3.x, do this step first, then follow the later guides in order. This guide follows the V4 migration guide at the last 4.x tag and the 4.0.0 README and examples, linked at the end.

What changed

  • Node.js 18 or a newer LTS release is required.
  • Callbacks are gone. Every method returns a Promise, and the last argument now overrides the fetch request.
  • Results are wrapped: const { data: user } = await management.users.get({ id }).
  • Top-level methods are removed: management.getUsers() becomes management.users.getAll(), auth.clientCredentialsGrant() becomes auth.oauth.clientCredentialsGrant().
  • Several methods are renamed, such as oauth.refreshToken to oauth.refreshTokenGrant and users.removeRoles to users.deleteRoles. User profiles come from UserInfoClient.getUserInfo.
  • Options change: proxy becomes an agent, supportedAlgorithms becomes idTokenSigningAlg, and scope, the tokenProvider cache options and includeResponseHeaders are removed from the Management client.
  • Legacy endpoints such as oauth.signIn, oauth.socialSignIn, tokens.getInfo and users.impersonate are removed.

Migration steps

  1. Upgrade the package

    npm install auth0@^4

    4.x supports Node.js 18 and newer LTS releases. The client class names are unchanged, and ES module imports work: import { ManagementClient, AuthenticationClient } from "auth0".

  2. Replace callbacks with await and unwrap data

    Before

    management.users.getAll({}, function (err, users) {
      console.log(users.length);
    });
    
    const user = await management.users.get({ id: "user-id" });
    console.log(user.email);

    After

    const { data: users } = await management.users.getAll();
    console.log(users.length);
    
    const { data: user } = await management.users.get({ id: "user-id" });
    console.log(user.email);
  3. Call methods on their namespaces

    Before

    const users = await management.getUsers();
    const user = await management.getUser({ id: userId });
    await management.updateUserMetadata({ id: userId }, { theme: "dark" });

    After

    const { data: users } = await management.users.getAll();
    const { data: user } = await management.users.get({ id: userId });
    await management.users.update({ id: userId }, { user_metadata: { theme: "dark" } });

    updateUserMetadata and updateAppMetadata are removed; send user_metadata or app_metadata through users.update. The guide's tables cover well over 100 renamed methods. Ones that commonly appear in application code:

    • getUsersByEmail to usersByEmail.getByEmail
    • getUserRoles to users.getRoles, and users.removeRoles to users.deleteRoles
    • createPasswordChangeTicket to tickets.changePassword
    • sendEmailVerification to jobs.verifyEmail, and jobs.errors to jobs.getErrors
    • organizations.getByID to organizations.get
    • getAccessToken to oauth.clientCredentialsGrant on an AuthenticationClient
  4. Update Authentication API calls

    Before

    auth.clientCredentialsGrant(
      { audience: "https://{YOUR_ACCOUNT}.auth0.com/api/v2/", scope: "read:users" },
      function (err, response) {
        // use response.access_token
      },
    );

    After

    const { data: tokens } = await auth.oauth.clientCredentialsGrant({
      audience: "https://{YOUR_ACCOUNT}.auth0.com/api/v2/",
    });
    // use tokens.access_token

    Grants live under auth.oauth, and scope is passed to the specific grant rather than set on the client. refreshToken becomes oauth.refreshTokenGrant, passwordGrant moves to oauth.passwordGrant, and passwordless calls move to passwordless.sendEmail, passwordless.sendSMS, passwordless.loginWithEmail and passwordless.loginWithSMS. For profiles, getProfile is replaced by a separate client:

    import { UserInfoClient } from "auth0";
    
    const userInfo = new UserInfoClient({ domain: process.env.AUTH0_DOMAIN });
    const { data: profile } = await userInfo.getUserInfo(accessToken);

    oauth.signIn is gone in favor of oauth.passwordGrant, and oauth.socialSignIn, delegation tokens and users.impersonate are removed with no direct replacement.

  5. Update client options

    Before

    const management = new ManagementClient({
      domain: "{YOUR_TENANT}.auth0.com",
      clientId: "{YOUR_CLIENT_ID}",
      clientSecret: "{YOUR_CLIENT_SECRET}",
      scope: "read:users update:users",
      tokenProvider: { enableCache: true, cacheTTLInSeconds: 10 },
    });

    After

    const management = new ManagementClient({
      domain: "{YOUR_TENANT}.auth0.com",
      clientId: "{YOUR_CLIENT_ID}",
      clientSecret: "{YOUR_CLIENT_SECRET}",
    });

    The Management client uses the Client Credentials grant, which receives every scope granted to the application, so scope was redundant. Each instance keeps its own cache holding a single access token; create a new client if you need a separate cache. A proxy setting becomes an https.Agent passed as agent. On the Authentication client, supportedAlgorithms is replaced by idTokenSigningAlg, which defaults to RS256; set it to HS256 if your ID tokens use that algorithm.

  6. Fix parameter shapes and error handling

    Before

    await management.connections.getAll({ fields: ["name", "strategy"] });
    
    await management.jobs.importUsers({
      users: fs.createReadStream("./myusers.json"),
      connection_id: "con_123",
    });

    After

    await management.connections.getAll({ fields: "name,strategy" });
    
    await management.jobs.importUsers({
      users: new Blob([fs.readFileSync("./myusers.json")], { type: "application/json" }),
      connection_id: "con_123",
    });

    fields takes a comma-separated string, and user imports take a Blob; the guide suggests the fetch-blob package's fileFrom if you do not want to read the whole file into memory. Errors are typed in 4.x. Management API failures throw ManagementApiError with statusCode and errorCode, Authentication API failures throw AuthApiError, and network failures and timeouts throw FetchError and TimeoutError:

    import { ManagementApiError } from "auth0";
    
    try {
      await management.users.get({ id: userId });
    } catch (e) {
      if (e instanceof ManagementApiError && e.statusCode === 404) return null;
      throw e;
    }

How to find every affected call

Find the clients first, then each removed shortcut you use. ast-grep matches the syntax tree, so calls split across lines are found too:

sg run --pattern 'new ManagementClient($$$ARGS)' --lang js
sg run --pattern 'new AuthenticationClient($$$ARGS)' --lang js
sg run --pattern '$MGMT.getUsers($$$ARGS)' --lang js
sg run --pattern '$MGMT.getUser($$$ARGS)' --lang js
sg run --pattern '$AUTH.clientCredentialsGrant($$$ARGS)' --lang js
sg run --pattern '$AUTH.getProfile($$$ARGS)' --lang js

$AUTH.clientCredentialsGrant also matches already-migrated auth.oauth.clientCredentialsGrant calls, because $AUTH can capture auth.oauth; skip those. Repeat with --lang ts. Callback-style calls and the .data unwrapping do not have one distinctive shape, so review every call returned by the client searches, and look for fields arrays.

Testing the change

  • Run tsc --noEmit. 4.x is written in TypeScript, so removed methods and missing data reads become type errors in TypeScript files.
  • Replace HTTP mocks. 4.x sends requests with fetch, and its client options accept a custom fetch implementation, which is a clean way to inject a fake in tests.
  • Run one Management call and one Authentication grant against a development tenant, not production.
  • Request a user that does not exist and confirm your handler sees a ManagementApiError with statusCode 404.

Automating it

We found no official codemod for this upgrade in the node-auth0 repository or its migration guide. The guide is a set of before and after tables, which is a good checklist but leaves the edits to you.

Versyka finds the call sites deterministically with ast-grep, rewrites them together with the code that reads the results (the data unwrapping, error branches, mocks), then runs your repository's install, typecheck, build and tests in a disposable sandbox. A pull request is opened only when those pass.

Was this guide helpful?

Check your own repository

Paste your package.json, requirements.txt or pyproject.toml into the free upgrade check to see which API SDKs are behind, with no account. Or let Versyka run this migration on one repository and open a pull request that has already passed your tests.

Sources