A CLI tool for migrating from identity providers into WorkOS. Supports Auth0, AWS Cognito, Clerk, Firebase Auth, and custom CSV — moving users, organizations, memberships, roles + permissions, password hashes, TOTP MFA factors, and SAML/OIDC SSO connections (created through the WorkOS Connections API).
The fastest way to get started is with the interactive wizard:
export WORKOS_SECRET_KEY=sk_...
npx @workos/migrations wizardThe wizard walks you through provider selection, export/transform, validation, and import step by step.
Run directly with npx (no install needed):
npx @workos/migrations <command>Or via the WorkOS CLI:
npx workos migrations <command>Or install globally:
npm install -g @workos/migrations
workos-migrate <command>| Command | Description |
|---|---|
export <provider> |
Unified export — write a migration package from any source (see below) |
wizard |
Interactive step-by-step migration wizard |
export-auth0 |
Deprecated alias for export auth0 (removed in v4.0) |
export-cognito |
Deprecated alias for export cognito (removed in v4.0) |
export-template |
Export a blank CSV template (users, saml_connections, oidc_connections, etc.) |
merge-passwords |
Merge Auth0 password hashes into the export CSV |
transform-clerk |
Deprecated alias for export clerk (removed in v4.0) |
transform-firebase |
Deprecated alias for export firebase (removed in v4.0) |
validate |
Validate a CSV file before import |
import |
Import users from CSV into WorkOS |
import-package |
Import a migration package directory into WorkOS |
generate-package-template |
Generate an empty migration package skeleton |
validate-package |
Validate a migration package against the contract |
analyze |
Analyze import errors and generate retry CSV |
enroll-totp |
Enroll TOTP MFA factors for imported users |
process-role-definitions |
Create roles and assign permissions in WorkOS |
Run npx @workos/migrations <command> --help for full option details on any command.
Every source is exported through one verb that always writes a migration package:
workos-migrate export <provider> --output-dir <dir> [provider options]| Provider | Ingest | Example |
|---|---|---|
auth0 |
API | export auth0 --domain … --client-id … --client-secret … --output-dir ./pkg |
cognito |
API | export cognito --region … --user-pool-ids … --output-dir ./pkg |
clerk |
file | export clerk --from-file clerk.csv --output-dir ./pkg |
firebase |
file | export firebase --from-file firebase.json --output-dir ./pkg |
csv |
file | export csv --output-dir ./pkg (writes a fillable skeleton) |
Credentials and options are generated per provider — run workos-migrate export <provider> --help to see them. SSO handoff (SAML/OIDC) is opt-in where supported: pass --secret-key for Clerk, or --service-account-key + --project-id for Firebase.
The legacy export-auth0, export-cognito, transform-clerk, and transform-firebase commands still work but print a deprecation notice and will be removed in v4.0.
Every migration is the same four steps — generate the package, validate it, optionally merge password hashes, then import:
# 1. Generate a migration package FROM your source provider
workos-migrate export auth0 \
--domain my-tenant.us.auth0.com \
--client-id <M2M_CLIENT_ID> \
--client-secret <M2M_CLIENT_SECRET> \
--output-dir ./migration
# 2. Validate it against the package contract
workos-migrate validate-package ./migration
# 3. (Auth0 only) Merge the password-hash export from Auth0 support
workos-migrate merge-passwords --package ./migration --passwords auth0-passwords.ndjson
# 4. Import users, organizations, memberships, roles, TOTP factors, and SSO connections into WorkOS
# (add --plan or --dry-run first to preview)
export WORKOS_SECRET_KEY=sk_...
workos-migrate import-package ./migrationThe package itself is a provider-neutral directory (users.csv, organizations.csv, organization_memberships.csv, role_definitions.csv, user_role_assignments.csv, totp_secrets.csv, sso/ connection CSVs, workos_upload/, and manifest.json) — see docs/migration-package.md for the full contract. Swap export auth0 for export cognito, export clerk --from-file …, export firebase --from-file …, or export csv to generate from a different source; steps 2–4 are identical.
The per-provider guides below cover provider-specific credential setup and options.
- Node.js 22.11+
- A WorkOS Secret Key (
WORKOS_SECRET_KEYenvironment variable)
The import CSV uses these columns:
| Column | Required | Description |
|---|---|---|
email |
Yes | User email address |
first_name |
No | First name |
last_name |
No | Last name |
email_verified |
No | true or false |
password_hash |
No | Password hash value |
password_hash_type |
No | bcrypt, firebase-scrypt, ssha, md5 |
external_id |
No | External identifier from source system |
metadata |
No | JSON string of custom metadata |
org_id |
No | WorkOS organization ID |
org_external_id |
No | External org identifier (looked up or auto-created) |
org_name |
No | Organization name (used with auto-creation) |
role_slugs |
No | Comma-separated role slugs for org membership |
The export and transform commands produce CSVs in this format automatically.
Auth0 is a parity-complete migration source. The end-to-end flow is:
- Run
export auth0to produce a migration package with users, organizations, memberships, roles, SSO handoff files, warnings, and the upload-compatible projection. For very large tenants,--engine bulk-jobis available; see step 3b. - Optionally run
merge-passwords --package <dir>to merge the Auth0 password export into the package. Unsupported hash algorithms are skipped with warnings instead of failing the merge. - Run
import-package <dir>to push organizations, users, memberships, roles, TOTP factors, and SSO connections into WorkOS in one shot. SSO connections are created through the Connections API; seedocs/auth0-sso-handoff.mdfor what the export captures and the manual fallback.
Create a Machine-to-Machine application in Auth0, authorize it for the Management API, and grant these scopes:
read:usersread:user_idp_tokensread:organizationsread:organization_membersread:organization_member_rolesread:rolesread:connectionsread:connections_options
read:connections_options is required for complete SAML/OIDC handoff exports because Auth0 stores
connection configuration inside the options object. If this scope is missing, later package phases
will warn and omit fields that cannot be read.
workos-migrate export auth0 \
--domain my-tenant.us.auth0.com \
--client-id <M2M_CLIENT_ID> \
--client-secret <M2M_CLIENT_SECRET> \
--output-dir ./migration-auth0This writes the full migration package: users.csv, organizations.csv,
organization_memberships.csv, warnings, and skipped-user sidecars, plus the
workos_upload/ projection (narrower WorkOS upload templates) and any sso/ handoff
artifacts.
To include the Auth0 role catalog and per-org role assignments alongside users, organizations, and memberships:
workos-migrate export auth0 \
--domain my-tenant.us.auth0.com \
--client-id <M2M_CLIENT_ID> \
--client-secret <M2M_CLIENT_SECRET> \
--entities users,organizations,memberships,roles \
--output-dir ./migration-auth0This writes role_definitions.csv and user_role_assignments.csv and merges the matched
role_slugs into users.csv and organization_memberships.csv. The
process-role-definitions command can then create the roles in
WorkOS and assign them to memberships. Note that the --use-metadata flow cannot fetch per-org
assignments from Auth0, so it writes the role catalog only and emits a warning.
To write only SSO handoff files:
workos-migrate export auth0 \
--domain my-tenant.us.auth0.com \
--client-id <M2M_CLIENT_ID> \
--client-secret <M2M_CLIENT_SECRET> \
--entities sso \
--output-dir ./migration-auth0-ssoOptions:
--orgs <ids...>- Filter to specific Auth0 organization IDs--entities <entities>- Comma-separated package entities to export (users,organizations,memberships,roles,sso)--rate-limit <n>- API requests per second (default: 50)--use-metadata- Useuser_metadatafor org discovery instead of the Organizations API--include-federated-users- Include federated/JIT users in package mode (skipped by default)--include-secrets- Include SSO connection secrets in package handoff files (redacted by default)--job-id <id>- Enable export checkpointing for large tenants--resume [jobId]- Resume a previously checkpointed export
The export maps Auth0 fields to WorkOS CSV format, including email_verified, external_id, and custom metadata.
Auth0 package SSO export inspects Auth0 enterprise strategies for SAML/OIDC configuration and emits only connections with enough reliable data for import-package to create them. Database, passwordless, social, generic OAuth, non-SAML/OIDC enterprise, and incomplete connections are skipped with warnings. With --include-secrets, Auth0 signingKey / decryptionKey pairs are carried into requestSigningKey/requestSigningCert and assertionEncryptionKey/assertionEncryptionCert so the importer can bring your own SP key pairs.
For a callback proxy reference implementation during Auth0 enterprise-connection cutover, see proxy-sample-auth0. The repo also includes proxy-sample-cognito for Cognito migrations.
Auth0 does not include password hashes in the Management API export. You need to request a password export from Auth0 support, which provides an NDJSON file. Once you have it, the CLI supports both legacy single-CSV merging and package-aware merging:
# Single CSV (legacy)
workos-migrate merge-passwords \
--csv auth0-export.csv \
--passwords auth0-passwords.ndjson \
--output auth0-with-passwords.csv
# Migration package — updates users.csv, workos_upload/users.csv, and the manifest
workos-migrate merge-passwords \
--package ./migration-auth0 \
--passwords auth0-passwords.ndjsonPackage mode warns and omits credentials for users whose hash algorithm is not supported by WorkOS imports (anything other than bcrypt or md5). Users without a matching hash are left without a password and will need to reset on first login.
For tenants where the Management API per-user fetch is too slow, package mode can use Auth0's users-exports job engine instead. This engine returns users without organization membership, so you'll typically run it alongside a Management API run that captured org/membership data, or follow up with a CSV-driven membership reconciliation.
workos-migrate export auth0 \
--domain my-tenant.us.auth0.com \
--client-id <M2M_CLIENT_ID> \
--client-secret <M2M_CLIENT_SECRET> \
--engine bulk-job \
--output-dir ./migration-auth0-bulkA bulk_export_no_org_membership warning is recorded in the package and bulk mode does not populate organizations.csv, organization_memberships.csv, or per-org role assignments.
Continue to Validation, Import, and Post-Import below.
Export your users from the Clerk Dashboard as a CSV file. The export includes columns like id, first_name, last_name, primary_email_address, password_digest, password_hasher, etc.
workos-migrate export clerk \
--from-file clerk-export.csv \
--output-dir ./migration-clerk \
--org-mapping orgs.csv \
--role-mapping roles.csvThis writes the canonical layout (users.csv, organizations.csv,
organization_memberships.csv, role_definitions.csv, user_role_assignments.csv,
workos_upload/, manifest, warnings, skipped users) so the result can be fed
straight into import-package. Unsupported password hashers are recorded as
warnings instead of failing the export.
To also pull Clerk enterprise SAML/OIDC connections into the sso/ handoff CSVs, pass
your Clerk Backend API key with --secret-key <sk_…> (or set CLERK_SECRET_KEY).
Options:
--from-file <path>- Path to the Clerk dashboard CSV export (required)--org-mapping <path>- CSV mapping Clerk user IDs to organizations (clerk_user_id,org_external_id,org_name)--role-mapping <path>- CSV mapping Clerk user IDs to roles (clerk_user_id,role_slug)--secret-key <key>- Clerk Backend API key; enables enterprise SSO connection export--source-tenant <name>- Optional tenant identifier recorded in the manifest.
The transformer handles:
- Field mapping (Clerk columns to WorkOS columns)
- bcrypt password passthrough (other hash types like argon2 are dropped with a warning since WorkOS does not support them)
- Username, phone number, and TOTP secret preservation in metadata
- Organization and role sidecar merging into the output CSV (or package)
Continue to Validation, Import, and Post-Import below.
The recommended path is export firebase, which writes a migration package ready for import-package.
Export your users from the Firebase Console or using the Firebase CLI (firebase auth:export). This produces a JSON file with a users array.
If you want to migrate passwords, get the hash parameters from Firebase Console > Authentication > Users > Password Hash Parameters. You need the signer key, salt separator, rounds, and memory cost.
workos-migrate export firebase \
--from-file firebase-export.json \
--output-dir ./migration-firebase \
--signer-key <BASE64_KEY> \
--salt-separator <BASE64_SEP> \
--rounds 8 \
--memory-cost 14 \
--org-mapping orgs.csvTo also pull Identity Platform SAML/OIDC configs into the sso/ handoff CSVs, pass a
service-account key file with --service-account-key <path> and --project-id <id> (or set
GOOGLE_APPLICATION_CREDENTIALS and GOOGLE_CLOUD_PROJECT / GCLOUD_PROJECT). If the
project ID cannot be resolved, the export fails rather than silently skipping SSO.
Options:
--from-file <path>- Path to the Firebase Auth JSON export (required)--source-tenant <name>- Optional tenant identifier recorded in the manifest.--name-split <strategy>- How to splitdisplayNameinto first/last:first-space(default),last-space, orfirst-name-only--include-disabled- Include disabled users (excluded by default)--skip-passwords- Skip password hash encoding--org-mapping <path>- CSV mapping Firebase UIDs to organizations (firebase_uid,org_external_id,org_name)--role-mapping <path>- CSV mapping Firebase UIDs to roles (firebase_uid,role_slug)--service-account-key <path>+--project-id <id>- Enable Identity Platform SSO connection export
The transformer handles:
- Firebase scrypt to PHC format encoding (
$firebase-scrypt$v=1$...) displayNamesplitting intofirst_nameandlast_name- Phone number, custom claims, and Firebase UID preservation in metadata
- Omitting metadata values that violate WorkOS limits (600 ASCII characters per value), with a warning
- Skipping users without an email address
Continue to Validation, Import, and Post-Import below.
Configure your AWS credentials using one of the standard methods:
- Environment variables (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN) - AWS credentials file (
~/.aws/credentials) - IAM role (when running on EC2/ECS/Lambda)
The Cognito exporter requires these IAM permissions:
cognito-idp:ListUserPoolscognito-idp:ListIdentityProviderscognito-idp:DescribeIdentityProvidercognito-idp:ListUsers
The recommended path is to write a migration package that the
import-package orchestrator can consume in a single step:
workos-migrate export cognito \
--region us-east-1 \
--user-pool-ids us-east-1_ABC123,us-east-1_DEF456 \
--output-dir ./migration-cognito \
--entities users,organizations,memberships,ssoThis writes the canonical layout: users.csv, organizations.csv,
organization_memberships.csv, the sso/ handoff CSVs, and the workos_upload/ projection.
By default each Cognito user pool maps to one WorkOS organization; pass
--org-strategy connection for one org per identity provider (memberships then become
header-only) or --org-strategy none to skip organization rows entirely.
Options:
--entities <list>-users,organizations,memberships,sso.--org-strategy <strategy>-user-pool(default),connection, ornone.--output-dir <dir>- Output directory for the migration package--saml-custom-entity-id-template <url>- Template for SAML custom Entity ID (default:urn:amazon:cognito:sp:{user_pool_id})--saml-custom-acs-url-template <url>- Template for SAML custom ACS URL (placeholders:{provider_name},{user_pool_id},{region})--oidc-custom-redirect-uri-template <url>- Template for OIDC custom redirect URI--skip-external-provider-users- Skip Cognito users whoseuserStatus=EXTERNAL_PROVIDER.
Note: Cognito does not expose password hashes via its API. The password_hash column will be blank for all users. Affected users will need to reset their password post-migration or rely on SSO + JIT provisioning via the migration proxy.
For a seamless cutover where existing IdP configurations continue to work, see the reference proxy implementation in proxy-sample-cognito/. This Lambda handler routes per-tenant traffic between Cognito and WorkOS during the migration window.
Continue to Validation, Import, and Post-Import below.
If you already have a CSV in WorkOS format (see CSV Format above), skip straight to validation:
workos-migrate validate --csv my-users.csv
workos-migrate import --csv my-users.csvFor unsupported providers, you can hand-build a migration package and run it through the same import-package orchestrator the dedicated providers use:
# Scaffold an empty package skeleton (generate-package-template is an equivalent alias)
workos-migrate export csv --output-dir ./migration-csv
# Populate users.csv (and optionally organizations.csv, organization_memberships.csv,
# role_definitions.csv, user_role_assignments.csv) with the canonical headers from
# docs/migration-package.md.
# Validate the package against the contract
workos-migrate validate-package ./migration-csv
# Run it through the importer (or --plan / --dry-run first)
workos-migrate import-package ./migration-csvvalidate-package checks the manifest schema, every canonical CSV header, every required file, manifest count consistency, and JSONL parseability. It's safe to run repeatedly — exit 0 means the package is ready for import-package.
Validate your CSV before importing to catch problems early:
workos-migrate validate --csv users.csvThe validator checks:
- Required fields (
emailis present and non-empty) - Email format
- Duplicate emails
- Password hash format (valid bcrypt or firebase-scrypt structure)
- Organization reference consistency
The --auto-fix flag corrects common issues automatically:
workos-migrate validate --csv users.csv --auto-fix --output users-fixed.csvAuto-fix handles whitespace trimming, email lowercasing, and empty field cleanup.
workos-migrate import --csv users.csvImport fails with a non-zero exit code when the CSV has no data rows, since that usually means an export produced nothing. Pass --allow-empty to treat it as a successful no-op instead.
User only (no org membership):
workos-migrate import --csv users.csvSingle org (all users into one organization):
workos-migrate import --csv users.csv --org-id org_01ABCMulti-org (org per row, from CSV columns):
workos-migrate import --csv users.csv --create-org-if-missingIn multi-org mode, the importer reads org_id, org_external_id, or org_name from each row. Organizations are cached in memory to avoid repeated API lookups. If --create-org-if-missing is set, organizations referenced by name or external ID that don't exist in WorkOS are created automatically.
workos-migrate import \
--csv users.csv \
--concurrency 20 \
--rate-limit 40 \
--workers 4 \
--chunk-size 5000 \
--job-id my-migration--concurrency <n>- Parallel API requests per worker (default: 10)--rate-limit <n>- Max requests per second across all workers (default: 40)--workers <n>- Worker threads for CPU distribution (default: 1, requires--job-id)--chunk-size <n>- Rows per checkpoint chunk (default: 1000)
The default import rate of 40 requests/second leaves headroom below the AuthKit write limit. User creation, membership creation, and their retries share the same request budget; importing a user with a membership normally takes two writes. The limiter allows an initial burst of up to one second's budget and then paces concurrent callers. Checkpoint chunks retain the same limiter, and worker threads share the coordinator's budget. Retries honor the server's Retry-After delay, including the Node SDK's retryAfter field, and otherwise use exponential backoff with jitter. Other traffic in the same environment can still cause throttling.
For large migrations, use --job-id to enable checkpointing. If the process crashes or is interrupted, resume from where it left off:
# Start with checkpointing
workos-migrate import --csv users.csv --job-id my-migration
# Resume after interruption
workos-migrate import --csv users.csv --resume my-migrationCheckpoint state is stored in .workos-checkpoints/<job-id>/.
Preview what the import would do without making any API calls:
workos-migrate import --csv users.csv --dry-runImport errors are written to a JSONL file (default: errors.jsonl). Each line contains the email, error type, HTTP status, and message for a single failure.
After an import, analyze errors to understand what went wrong and generate a retry CSV:
workos-migrate analyze \
--errors errors.jsonl \
--retry-csv retry.csv \
--original-csv users.csvThe analyzer groups errors by pattern, classifies them as retryable or non-retryable, and suggests fixes. The retry CSV contains only the rows that failed with retryable errors, so you can re-import just those users.
Migration packages produced by export <provider> can be imported in one step with import-package:
# Plan only — print what would happen and exit
workos-migrate import-package ./migration-auth0 --plan
# Dry run — validate the package and write workos_import_summary.json with status=planned
workos-migrate import-package ./migration-auth0 --dry-run
# Live import
workos-migrate import-package ./migration-auth0Like import, import-package fails when every entity CSV in the package is header-only; --allow-empty opts back into importing nothing.
The orchestrator runs entities in this order:
- Organizations (resolved or created during user import via
--create-org-if-missingsemantics). - Users + memberships (
runImportonusers.csv). - Role definitions (
process-role-definitionsonrole_definitions.csv). - User-role assignments (per-org slices of
user_role_assignments.csv). - TOTP enrollment (
enroll-totpontotp_secrets.csv). - SSO connections — created through the WorkOS Connections API (
POST /connections) fromsso/saml_connections.csvandsso/oidc_connections.csv. See SSO connections below.
Every run writes workos_import_summary.json (or --summary <path>) with per-entity status, totals, succeeded/failed counts, and warnings. Per-row errors land in workos_import_errors.jsonl (or --errors <path>).
For every SAML/OIDC row the importer resolves the WorkOS organization (by organizationId, or by organizationExternalId — creating it from organizationName when missing), adds the exported domains to the organization (verified by default; see --sso-domains), and calls POST /connections with the IdP configuration, legacy ACS URL / entity ID overrides, attribute mappings, and any bring-your-own SP key pairs. Every row needs a stable externalId: creation is idempotent on it, so re-running a package is safe, and rows without one (or with a duplicate) are skipped rather than risk creating duplicate connections. --plan runs the same mapping offline and lists which rows will be created or skipped and why.
Outputs:
workos_sso_connections.csvin the package root — one row per connection with the WorkOS organization id, connection id,callback_endpoint, ACS URL / SP entity ID (SAML) or redirect URI (OIDC), outcome (created,existing,skipped,failed,not_attempted), and warnings.existingmeans a connection with thatexternalIdalready existed in the organization and was left unchanged.sso/proxy_routes.csv—workosConnectionIdandworkosAcsUrlare filled in for created connections so a callback proxy can be configured without a manual mapping step.
Options:
--skip-sso- Do not create connections; reportsso/files as handoff.--sso-secrets <path>- OIDC client secrets keyed by connectionexternalId(JSON object, JSON array of{ externalId, clientSecret }, or CSV). Exporters redactclientSecretby default and the API requires it, so OIDC rows without a secret are skipped.--sso-custom-attributes <include|skip>- Custom attribute mappings (sso/custom_attribute_mappings.csv) must already exist in the WorkOS dashboard. Interactive runs prompt to include them, continue without them, or abort; pass the flag for non-interactive runs.--sso-rate-limit <n>- Connections API requests per second (default: 5).--sso-domains <verified|pending|skip>- How exporteddomainsare applied to organizations.verified(default) trusts the source provider's ownership proof and enables domain-based routing immediately;pendingadds them for the customer to verify in the dashboard;skipleaves organization domains untouched. Existing domains in states other than verified/pending are never rewritten. If a domain update fails or cannot preserve existing states, the connection is not created and the row is reported as failed in the results and error output. Fix the organization's domains and re-run, or use--sso-domains skipto manage domains separately.
Requirements and limits:
- The Connections API migration capabilities (
POST /connections) are enabled per environment by WorkOS. If they are off, the run reports SSO ashandoffwith the reason; ask WorkOS to enable them and re-run. - New connections start in the
validatingstate and activate on the first successful sign-in. - IdP-initiated SSO and NameID format overrides have no API field yet and are surfaced as warnings; configure them in the dashboard after import.
- Environments that restrict organizations to one SSO connection reject a second connection for the same organization (
connection_already_exists_for_organization); give each connection its own organization or ask WorkOS to lift the restriction. - SP key pairs are accepted only at creation; without both key and cert columns WorkOS generates the SP keys and the customer IdP must be updated with the new SP certificate.
If your source system has TOTP secrets (e.g., from Auth0 or Clerk), you can enroll them in WorkOS after import:
workos-migrate enroll-totp \
--input totp-secrets.csv \
--totp-issuer "MyApp"The input file can be CSV (email,totp_secret) or NDJSON (one JSON object per line with email and totp_secret or mfa_factors fields). Format is auto-detected from the file extension.
Options:
--format <csv|ndjson>- Override auto-detection--concurrency <n>- Parallel requests (default: 5)--rate-limit <n>- Requests per second (default: 10)--totp-issuer <name>- Issuer shown in authenticator apps--dry-run- Validate without enrolling
Create roles and permissions in WorkOS from a CSV, then assign them to users:
# Create roles and permissions
workos-migrate process-role-definitions \
--definitions role-definitions.csv
# Create roles and assign to users
workos-migrate process-role-definitions \
--definitions role-definitions.csv \
--user-mapping user-roles.csv \
--org-id org_01ABCRole definitions CSV (role_slug,role_name,role_type,permissions[,org_id]):
role_slug,role_name,role_type,permissions
admin,Administrator,environment,"read,write,delete"
viewer,Viewer,environment,read
org-admin,Org Admin,organization,"read,write",org_01ABCUser-role mapping CSV (email,role_slug):
email,role_slug
alice@example.com,admin
bob@example.com,viewernpm install
npm run build
npm run dev # Run with tsx (no build step)
npm run lint
npm run typecheck
npm test # 120 tests across 10 suitesSee CONTRIBUTING.md for development setup and guidelines. Security issues should be reported privately as described in SECURITY.md.
MIT. See LICENSE.