Auth0 user imports are surprisingly not about bulk creation, but a delicate dance of identity reconciliation.

Let’s watch a user get imported. Imagine a user, Alice, who currently exists in your old system with user_id: "old-system-123" and email alice@example.com.

{
  "email": "alice@example.com",
  "user_id": "old-system-123",
  "connection": "Initial-Migration-Connection",
  "email_verified": true,
  "app_metadata": {
    "migration_source": "old-system"
  }
}

When Auth0 processes this JSON object, it uses a combination of email and connection to determine if a user already exists. If alice@example.com is already in Initial-Migration-Connection, Auth0 will try to link this imported record to the existing one. The crucial part here is user_id. If old-system-123 is not already present in Auth0, a new user will be created with that user_id. If old-system-123 is present, the imported record will be merged with the existing user. This means the app_metadata you’re importing will be added to the user’s existing app_metadata.

The primary problem Auth0’s import/export feature solves is migrating users from an existing identity provider or database into Auth0 without requiring users to re-authenticate immediately. This is achieved by exporting user data in a specific JSON format and then importing it into a designated Auth0 "connection." The import process essentially seeds Auth0 with user accounts, allowing for a phased migration where existing credentials (or at least their hash, if you’re careful) can be brought over, or users can be prompted to reset their password on their first login to Auth0.

Here’s a look at the actual import process in action. After preparing your users.json file, you’ll use the Auth0 Management API or the Dashboard. Using curl with the Management API:

curl 'https://YOUR_DOMAIN.auth0.com/api/v2/jobs/users-imports' \
  -X POST \
  -H 'Authorization: Bearer YOUR_MANAGEMENT_API_TOKEN' \
  -H 'Content-Type: application/json' \
  --data '{
    "connection_id": "CON_abcdef1234567890",
    "upsert": true,
    "send_completion_email": true,
    "users": [
      {
        "email": "alice@example.com",
        "user_id": "old-system-123",
        "connection": "Initial-Migration-Connection",
        "email_verified": true,
        "app_metadata": {
          "migration_source": "old-system"
        }
      },
      {
        "email": "bob@example.com",
        "user_id": "old-system-456",
        "connection": "Initial-Migration-Connection",
        "email_verified": false,
        "password_hash": "your_bcrypt_hash_here",
        "password_algorithm": "bcrypt"
      }
    ]
  ]}'

The connection_id refers to the target Auth0 connection where these users will reside. upsert: true is critical: it means if a user with a matching identifier (email and connection, or user_id if provided) already exists, they will be updated (upserted) rather than causing an error. The users array contains the actual user objects. Notice bob@example.com includes a password_hash and password_algorithm. This is the secure way to migrate existing password hashes if your old system stored them. Auth0 supports bcrypt, scrypt, and PBKDF2. If you don’t provide a hash, users will typically be forced to reset their password on their first login.

The entire mental model revolves around the concept of a "connection" in Auth0. A connection is essentially a source of users. When you import users, you are populating a specific connection. You can have multiple connections for different purposes (e.g., Initial-Migration-Connection, Database-Users, Social-Logins). The user_id field in the import JSON is a globally unique identifier within Auth0. If you provide a user_id that already exists in Auth0, even in a different connection, the import will attempt to merge the user data. This can be powerful but also dangerous if not managed carefully. The email and connection combination is the primary lookup mechanism for new user creation or matching.

When migrating, especially with password hashes, the password_hash field in your import JSON must be a pre-hashed value. You cannot simply put the plaintext password here. You’ll need to use the same hashing algorithm and salt (if applicable) that your original system used to generate the hash, and then provide that resulting hash string along with the password_algorithm identifier to Auth0. If you skip this, Auth0 won’t know the user’s password, and they’ll get a reset prompt.

The next hurdle you’ll likely encounter is managing user roles and permissions during or after the migration.

Want structured learning?

Take the full Auth0 course →