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
fetchrequest. - Results are wrapped:
const { data: user } = await management.users.get({ id }). - Top-level methods are removed:
management.getUsers()becomesmanagement.users.getAll(),auth.clientCredentialsGrant()becomesauth.oauth.clientCredentialsGrant(). - Several methods are renamed, such as
oauth.refreshTokentooauth.refreshTokenGrantandusers.removeRolestousers.deleteRoles. User profiles come fromUserInfoClient.getUserInfo. - Options change:
proxybecomes anagent,supportedAlgorithmsbecomesidTokenSigningAlg, andscope, thetokenProvidercache options andincludeResponseHeadersare removed from the Management client. - Legacy endpoints such as
oauth.signIn,oauth.socialSignIn,tokens.getInfoandusers.impersonateare removed.
Migration steps
Upgrade the package
npm install auth0@^44.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".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);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" } });updateUserMetadataandupdateAppMetadataare removed; senduser_metadataorapp_metadatathroughusers.update. The guide's tables cover well over 100 renamed methods. Ones that commonly appear in application code:getUsersByEmailtousersByEmail.getByEmailgetUserRolestousers.getRoles, andusers.removeRolestousers.deleteRolescreatePasswordChangeTickettotickets.changePasswordsendEmailVerificationtojobs.verifyEmail, andjobs.errorstojobs.getErrorsorganizations.getByIDtoorganizations.getgetAccessTokentooauth.clientCredentialsGranton anAuthenticationClient
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_tokenGrants live under
auth.oauth, andscopeis passed to the specific grant rather than set on the client.refreshTokenbecomesoauth.refreshTokenGrant,passwordGrantmoves tooauth.passwordGrant, and passwordless calls move topasswordless.sendEmail,passwordless.sendSMS,passwordless.loginWithEmailandpasswordless.loginWithSMS. For profiles,getProfileis 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.signInis gone in favor ofoauth.passwordGrant, andoauth.socialSignIn, delegation tokens andusers.impersonateare removed with no direct replacement.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
scopewas redundant. Each instance keeps its own cache holding a single access token; create a new client if you need a separate cache. Aproxysetting becomes anhttps.Agentpassed asagent. On the Authentication client,supportedAlgorithmsis replaced byidTokenSigningAlg, which defaults toRS256; set it toHS256if your ID tokens use that algorithm.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", });fieldstakes a comma-separated string, and user imports take aBlob; the guide suggests the fetch-blob package'sfileFromif you do not want to read the whole file into memory. Errors are typed in 4.x. Management API failures throwManagementApiErrorwithstatusCodeanderrorCode, Authentication API failures throwAuthApiError, and network failures and timeouts throwFetchErrorandTimeoutError: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 missingdatareads become type errors in TypeScript files. - Replace HTTP mocks. 4.x sends requests with
fetch, and its client options accept a customfetchimplementation, 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
ManagementApiErrorwithstatusCode404.
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.