> For the complete documentation index, see [llms.txt](/docs/llms.txt)

# Migrate From Stytch | FusionAuth Docs

How to migrate your users from Stytch to FusionAuth.

# Migrate From Stytch

This document will help you migrate users from Stytch to FusionAuth.

There are a number of different ways applications can be integrated with Stytch, and it would be difficult to cover them all. This guide is a low-level, technical tutorial focusing on transferring password hashes, calling APIs, and preparing data when migrating users from a Consumer Authentication project. The steps outlined here have not been tested with the Stytch B2B SaaS Authentication project type.

This guide explains how to import passwords into FusionAuth, but does not deal with other Stytch authentication types like magic links, passkeys, passcodes, mobile biometrics, two-factor authentication, and social logins such as Google OAuth.

For an explanation of the high-level process of a migration strategy, see the [migration overview article](/docs/lifecycle/migrate-users). Be aware of all laws regarding the protection and transfer of personal information in your country.

## Prerequisites[#](#prerequisites)

If you want to import user passwords in addition to user personal details, you need a basic understanding of how password hashing and salts work. FusionAuth has a [hashing article](/articles/security/math-of-password-hashing-algorithms-entropy) that is a good starting point.

To follow this tutorial, you need:

*   [Node.js](https://nodejs.org/en) to run the migration scripts, and npm.
*   [FusionAuth](/download). The easiest way to run it locally is to use [Docker](https://docs.docker.com/get-docker/) with the configuration file provided later in this tutorial.

Stytch also has SDKs for [Go, Java, Python, and Ruby](https://stytch.com/docs/sdks) if you prefer to convert the JavaScript scripts accompanying this tutorial to another language for your migration in production.

## Planning Considerations[#](#planning-considerations)

### Obtaining User Data[#](#obtaining-user-data)

You can use the [Stytch API](https://stytch.com/docs/api) to export user data, but you cannot export password hashes via the API. To get password hashes, you will need to email Stytch support as described in the [Request User Passwords From Stytch](#request-user-passwords-from-stytch) section.

### Mapping User Attributes[#](#mapping-user-attributes)

The attributes of the User object in FusionAuth are [well documented](/docs/apis/users).

If there is an attribute in your user which cannot be directly mapped to a FusionAuth attribute, you can place it in the **user.data** field. This field can store arbitrary JSON values and will be indexed and searchable.

### Social Logins[#](#social-logins)

Stytch also provides integrations with other social login providers such as Twitter, Google or Facebook. Review the supported FusionAuth [Identity Providers](/docs/lifecycle/authenticate-users/identity-providers/) to ensure your social providers are supported.

If not supported explicitly, a provider may work with an OIDC or SAML connection. Otherwise, please open a [feature request](https://github.com/fusionauth/fusionauth-issues/).

When migrating social logins, you may need to modify the switches of the Stytch import script. See [Use the Script](#use-the-script) for more.

Migrating users with social logins such as Apple or Facebook requires that you have an existing user Id for that provider. What this unique user Id looks like depends on the particular social identity provider. The unique Id may be an email address, an integer, UUID, or a random string.

Configure the appropriate FusionAuth Identity Provider with the same values (`client_id`, etc) as the original user management system you are migrating away from.

Import users with the [Import API](/docs/apis/users/import), assigning each user with a social login a random password such as a UUID.

Your next step depends on whether the social login provider's unique identifier is available as part of your migration data. If you have the social login provider's unique identifier, for each user, use the [Link API](/docs/apis/identity-providers/links) to create a link with the appropriate User Id, Identity Provider Id, and Identity Provider User Id.

*   The User Id is the Id of the recently created FusionAuth User.
*   The Identity Provider Id is found on the corresponding [Identity Provider API documentation](/docs/apis/identity-providers/). Look for **identityProvider.id**.
*   The Identity Provider User Id is the existing social provider user identifier exported or otherwise extracted from the original system.

You do not need to migrate the social network token, which may or may not be accessible. During the first login of a newly migrated user, FusionAuth finds the unique user in the social login provider based on the migrated Identity Provider User Id, and completes the login. During this process, FusionAuth stores a token on the Link, if the social provider returns one. Depending on the configuration of the social provider, users may see a prompt asking if they want to allow FusionAuth to have access to user data such as email address.

IdP linking strategies have been available since version 1.28.0. Before that version, users were linked on email.

If you do not have the social login provider's identifier, you need to decide if you want to transparently link the two accounts, which is easier for the end user, or if you want to ask the user to manually link the accounts, which is more accurate, but may be confusing.

To transparently link the accounts, choose a linking strategy of `Link On Email` or `Link On Username`, which will create the user if they don't exist. However, if the user has an email address at their social provider which differs from the email address that was used to sign up for your application and which you imported to FusionAuth, then two accounts will be created.

For example, if the user has a Google account `richard@gmail.com`, but signed up for your application with `richard@fusionauth.io`, then if you use the `Link On Email` strategy, two different accounts will be created, since FusionAuth is trying to match on email address and they don't. The same holds true for usernames with the `Link on Username` strategy.

To prompt the user to link the accounts, choose a linking strategy of `Pending`, which will prompt the end user to sign into FusionAuth after they sign into the social provider, authoritatively linking the two accounts.

Here's more information about [IdP Linking Strategies](/docs/lifecycle/authenticate-users/identity-providers/#linking-strategies).

Apple only returns a user's full name the first time the user logs in. Since you have users who have already used Apple to log in to your previous authentication gateway, Apple will not provide the user's name when they log in to FusionAuth now. The only solution is to import the user's name manually from your previous database into FusionAuth.

### Other Entities[#](#other-entities)

There are often other important entities, such as connections or roles, that need to be migrated. There are usually fewer of these, so an automated migration may not make sense, but plan to move this configuration somehow.

Be aware that functionality may not be the same between Stytch and FusionAuth. This is different from user data; as long as you can somehow migrate a login identifier (a username or email) and a password hash, a user will be authenticated and successfully migrated. You can [download FusionAuth](/download/) before you begin a migration and build a proof of concept to learn more about the differences.

A partial list of what may need to be migrated for your application to work properly includes the following:

*   In Stytch, sign-in providers are a source of data for users. FusionAuth calls these Identity Providers.

#### Identifiers[#](#identifiers)

When creating an object with the FusionAuth API, you can specify the Id. It must be a [UUID](/docs/reference/data-types#uuids).

This works for users, applications, and tenants, among others.

If you have external dependencies on an Id stored in Stytch, port the same Id over to FusionAuth.

## Exporting Users[#](#exporting-users)

To export users from Stytch, you need to request the user password hashes from Stytch via email, get all user details via the API, and then combine the user details with the hashes.

### Create Stytch Users[#](#create-stytch-users)

You probably already have a Stytch account with users you want to export to FusionAuth. Even so, it is a good idea to create a separate test project in Stytch with only a few users. You can use the test project to run an end-to-end migration between Stytch and FusionAuth quickly, without having to handle millions of real users and the privacy risks of handling their data when encountering errors.

Create a new Stytch project:

*   Create a Stytch account or sign in to your existing account at [https://stytch.com/dashboard](https://stytch.com/dashboard).
*   Locate the project button next to the workspace button in the header at the top left and use the project button to create a new project you'll use for test users.
*   Under Configuration on the sidebar, select API Keys .
*   Note the project Id and secret.

Create three example users:

*   Download this tutorial's scripts repository from [https://github.com/fusionauth/fusionauth-import-scripts](https://github.com/fusionauth/fusionauth-import-scripts) and unzip it, or if you have Git, run the code below.
    
    ```
    git clone https://github.com/fusionauth/fusionauth-import-scripts.git
    ```
    
*   In the file `fusionauth-import-scripts/stytch/js/1_makeStytchUsers.mjs`, change the project Id and secret to match your project.
    
*   In a terminal, run the code below to create three new users in Stytch using the Stytch JavaScript API.
    
    ```
    cd fusionauth-import-scripts/stytch/js
    npm update
    node 1_makeStytchUsers.mjs
    ```
    
*   In the Stytch web dashboard, check that the users now exist.
    
*   If any errors occur and you need to delete the users, uncomment the lines with `client.users.delete`, set the user Ids from the dashboard, and rerun the script.
    

The `1_makeStytchUsers.mjs` file uses the Stytch JavaScript SDK to create users with three different hashing algorithms. However, the line `client.passwords.authenticate` provides the clear text password to Stytch, which Stytch uses to rehash the user password using the Scrypt hashing algorithm. At the end of this script, all your users' passwords will be hashed with Scrypt in the Stytch database.

Even though you can create passwords for Stytch users with different hashing algorithms ([bcrypt, scrypt, Argon2, MD5, SHA-1, or PBKDF2](https://stytch.com/docs/api/password-migrate)), scrypt is the Stytch-preferred algorithm. Whenever a user logs in, Stytch will verify their password hash using the algorithm it is currently stored with, and then rehash their password using scrypt and discard the old hash. Password rehashing is a common technique used to upgrade security as hashing algorithms evolve.

Once you have completed this tutorial and understand how to import hashes into FusionAuth, you might want to go back and comment out the `authenticate` line to test other hashing algorithms. Note that this is necessary only in the rare case that you previously imported users into Stytch from another system, and some of those users have not yet logged in again.

### Request User Passwords From Stytch[#](#request-user-passwords-from-stytch)

You cannot download your users' password hashes from Stytch using their API. To get the password hashes, email `support@stytch.com` from the email address you used to sign up with Stytch and ask for your users' hashes to be sent to you.

Stytch will encrypt the hashes with your public key. Attach this key in a `.pem` file to the email you send Stytch.

To create a private and public key pair, use the commands below in a terminal.

```
openssl genpkey -algorithm RSA -out private_key.pem &&
openssl rsa -pubout -in private_key.pem -out public_key.pem
```

Email Stytch only `public_key.pem`. Keep `private_key.pem` secret and secure.

An example of what these keys look like is in the directory `fusionauth-import-scripts/stytch/exampleData/1_emailRequest`. You can use the keys to request test users from Stytch but do **not** use these keys for real users, as they are publicly available on GitHub.

If you don't want to send Stytch an email, you can use the example response files for the rest of this tutorial. The two files Stytch emailed are in `exampleData/2_emailResponse`. Remember to change the sample user Ids to your actual user Ids as you proceed.

### Decrypt The Reply[#](#decrypt-the-reply)

Stytch will reply with two files: An encrypted password hash file (`stytch-project-test-36510638-652a-4d3d-9a94-f0a7106582fc-hashes-2021-01-11.enc`) and the key to decrypt it (`key.bin.enc`).

Decrypt the password file with the commands below.

```
openssl pkeyutl -decrypt -inkey private_key.pem -in key.bin.enc -out key.bin &&
openssl enc -d -aes-256-cbc -in stytch-project-test-36510638-652a-4d3d-9a94-f0a7106582fc-hashes-2021-01-11.enc -out stytch_password_hashes.csv -pass file:./key.bin
```

Now you have all your user hashes in the file `stytch_password_hashes.csv`. Below is the `exampleData/3_responseDecryption/stytch_password_hashes.csv` file content for three users.

```
scrypt hash parameters,,,,
scryptCostParameter,1 << 15,,,
scryptRParameter,8,,,
scryptPParameter,1,,,
scryptKeyLengthParamenter,32,,,
,,,,
id,hash,salt,hash_method,project_id
user-test-178d8ee5-c458-4f48-a482-8fefa30a1a87,uiOC_BwbKta9R9QL6Ss6KTDpCcULh9_zhObl5j4398M=,BbV-sGQqUIX1NwE6uqtSITv4fa1iMw==,scrypt,project-test-36510638-652a-4d3d-9a94-f0a7106582fc
user-test-799ea981-b2bb-417f-afe8-4ab6e79fcd2a,A6VzdsTsTHPqafORIb0GkGD6qFxdncqwA15YXRsVgvs=,XPapDAm6xdV5UhMpyPrSpy8FbfCDtA==,scrypt,project-test-36510638-652a-4d3d-9a94-f0a7106582fc
user-test-c1be12e4-fc61-4e7a-8831-0fc14cb87b65,8dg6AaIWPfcLTQU7lb4H-CI49dHeqaBXfFE1ogb2qRQ=,zKia-0BdIFKCzWbzXbj3qrhBnbiWNg==,scrypt,project-test-36510638-652a-4d3d-9a94-f0a7106582fc
```

The file header describes the parameters you need to use when you run the scrypt hashing algorithm to convert user passwords to match the hashes in this file.

Below the header are the column names, and then one row per user. In the example data, all hashes were made using scrypt.

#### Common Problems With Hashes[#](#common-problems-with-hashes)

Not all hash algorithms use separate salts. For instance, bcrypt will create a salt automatically when hashing a password, and store the hash and salt concatenated in one string. You may have to deal with the peculiarities of different algorithms like this if you previously migrated users into Stytch from an authentication provider that did not use scrypt.

Even when using scrypt with the given parameters, you need to carefully check that how you hash passwords has the same result as Stytch. Hashes and salts are arrays of bytes (numbers) and therefore cannot be displayed directly as text. They are instead mapped to text using a conversion process called Base64. However, there are [different ways](https://en.wikipedia.org/wiki/Base64#Variants_summary_table) to do this mapping. Since you can see the hashes from Stytch use `-` and `_`, they must use RFC 4648 (the URL-safe standard). This can cause miscommunications with other hash libraries that use `+` and `/`.

For instance, we can use the snippet of JavaScript Node.js code below in `js/2_checkHash.mjs` to create a hash for the last user in the example file.

```
import * as crypto from 'crypto';

const password = 'averylongandunguessablepasswordwithlotsofrandominfooofisjoafasnr;,n2';
const salt = 'zKia-0BdIFKCzWbzXbj3qrhBnbiWNg==';
const hash = '8dg6AaIWPfcLTQU7lb4H-CI49dHeqaBXfFE1ogb2qRQ=';

const cost = 1 << 15;
const blockSize = 8;
const parallelization = 1;
const keyLength = 32;

crypto.scrypt(password, salt, keyLength, { N: cost, r: blockSize, p: parallelization, maxmem: 1024 * 1024 * 1024*50 }, (err, derivedKey) => {
  if (err) throw err;
  const keyBase64 = derivedKey.toString('base64'); //  .replace(/\+/g, '-').replace(/\//g, '_')
  console.log('Hash:', keyBase64);
  console.log('Matches provided hash:', keyBase64 === hash);
});
```

Note that our hash algorithm looks correct, but the hashes differ by one character, `+`, in `8dg6AaIWPfcLTQU7lb4H-CI49dHeqaBXfFE1ogb2qRQ=` and `8dg6AaIWPfcLTQU7lb4H+CI49dHeqaBXfFE1ogb2qRQ=`.

To have the hashes match correctly, you need to replace the `+` and `/` characters in your hash:

```
const keyBase64 = derivedKey.toString('base64').replace(/\+/g, '-').replace(/\//g, '_');
```

JavaScript and FusionAuth use the `+` and `/` symbols. Stytch and the Java scrypt plugin use `-` and `_`. If you encounter an error when verifying hashes, try converting the hash and salt to use the other character set.

### Get All User Details And Combine With Hashes[#](#get-all-user-details-and-combine-with-hashes)

Stytch emails you only the user hash and Id. You need to use the Stytch API to retrieve all user details from Stytch, then combine those with the hash from the email, and save the user to FusionAuth.

Open your hash file from Stytch in a text editor and remove all the header lines so only one user per row remains. Save this file to `fusionauth-import-scripts/stytch/js/hash.csv`. The file should look like `exampleData/4_hashFilePreparation/hash.csv` now.

```
user-test-178d8ee5-c458-4f48-a482-8fefa30a1a87,uiOC_BwbKta9R9QL6Ss6KTDpCcULh9_zhObl5j4398M=,BbV-sGQqUIX1NwE6uqtSITv4fa1iMw==,scrypt,project-test-36510638-652a-4d3d-9a94-f0a7106582fc
user-test-799ea981-b2bb-417f-afe8-4ab6e79fcd2a,A6VzdsTsTHPqafORIb0GkGD6qFxdncqwA15YXRsVgvs=,XPapDAm6xdV5UhMpyPrSpy8FbfCDtA==,scrypt,project-test-36510638-652a-4d3d-9a94-f0a7106582fc
user-test-c1be12e4-fc61-4e7a-8831-0fc14cb87b65,8dg6AaIWPfcLTQU7lb4H-CI49dHeqaBXfFE1ogb2qRQ=,zKia-0BdIFKCzWbzXbj3qrhBnbiWNg==,scrypt,project-test-36510638-652a-4d3d-9a94-f0a7106582fc
```

Check the fourth column to ensure that only scrypt users are included. The first four columns are the most important: `id`, `hash`, `salt`, and `hash_method`.

The `3_getUserDetails.mjs` script loops through each user in the `hash.csv` file, uses the API to get the user details from Stytch, and saves them to `js/users.json`.

*   Open `3_getUserDetails.mjs` and set your project Id and secret.
*   Make sure the `hash.csv` is the same directory `fusionauth-import-scripts/stytch/js` where the `3_getUserDetails.mjs` script is located.
*   Run the commands below in a terminal in the `fusionauth-import-scripts/stytch/js` directory.
    
    ```
    npm update
    node 3_getUserDetails.mjs
    ```
    

Open `js/users.json`. It should look like the sample in `exampleData/5_userDetailAndHashPreparation/users.json`.

```
[
  {
    "biometric_registrations": [],
    "created_at": "2024-01-11T09:54:59Z",
    "crypto_wallets": [],
    "emails": [
      {
        "email": "user1@example.com",
        "email_id": "email-test-8af9b269-8f2f-44a6-8322-6f2f276cba5d",
        "verified": false
      }
    ],
    "name": {
      "first_name": "A",
      "last_name": "Example",
      "middle_name": ""
    },
    "password": {
      "password_id": "password-test-80e44428-9182-4635-8a08-9c4c5ccf6c72",
      "requires_reset": false
    },
    "phone_numbers": [
      {
        "phone_id": "phone-number-test-b4c5c640-3f30-46bc-8bdf-5379ad21367b",
        "phone_number": "+272223334444",
        "verified": false
      }
    ],
    "providers": [],
    "request_id": "request-id-test-d996f3a8-ba0c-4bff-9214-2451f42bceb0",
    "status": "active",
    "status_code": 200,
    "totps": [],
    "trusted_metadata": {},
    "untrusted_metadata": {},
    "user_id": "user-test-178d8ee5-c458-4f48-a482-8fefa30a1a87",
    "webauthn_registrations": [],
    "hash": "uiOC_BwbKta9R9QL6Ss6KTDpCcULh9_zhObl5j4398M=",
    "salt": "BbV-sGQqUIX1NwE6uqtSITv4fa1iMw==",
    "hashAlgorithm": "scrypt"
  }
]
```

In Stytch, a user can have multiple emails and phone numbers. FusionAuth allows for only one. You will store the extras in the user data **JSON** field when migrating the user, along with any other Stytch fields FusionAuth does not support.

The last lines of the object show the hash and salt that the script added from the hash file to the user details from the API.

## Importing Users[#](#importing-users)

First install a FusionAuth plugin to handle the Stytch password hash algorithm, then import the users, and finally verify the import.

### Build The Scrypt Password Hash Plugin For FusionAuth[#](#build-the-scrypt-password-hash-plugin-for-fusionauth)

The scrypt hashing algorithm is not [natively supported by FusionAuth](/docs/reference/password-hashes). However, FusionAuth allows [custom plugins](/docs/extend/code/password-hashes/custom-password-hashing). There is a scrypt plugin accompanying this article [in this GitHub repository](https://github.com/FusionAuth/fusionauth-contrib/tree/main/Password%20Hashing%20Plugins).

The linked repository has multiple plugin projects. You need to build and install only the hashing project.

Download and unzip the repository, or use Git in a terminal with the code below.

```
git clone https://github.com/fusionauth/fusionauth-contrib.git
```

Open the file `fusionauth-contrib/Password Hashing Plugins/src/main/java/com/mycompany/fusionauth/plugins/ExampleStytchScryptPasswordEncryptor.java`. The content of the file is shown below.

```
/*
 * Copyright (c) 2020, FusionAuth, All Rights Reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */
package com.mycompany.fusionauth.plugins;

import io.fusionauth.plugin.spi.security.PasswordEncryptor;
import com.lambdaworks.crypto.SCrypt;
import org.apache.commons.codec.binary.Base64;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class ExampleStytchScryptPasswordEncryptor implements PasswordEncryptor {

// tag::scryptParameters[]
  /* Scrypt Parameters. You can find the correct settings for your Stytch project
    in the email they sent you containing your hashes. Copy them here. */
  private static final int N_CpuCost = 1 << 15;
  private static final int R_MemoryCost_BlockSize = 8;
  private static final int P_Parallelization = 1;
  private static final int KeyLength = 32;
// end::scryptParameters[]

  @Override
  public int defaultFactor() {
    return 0;
  }

  @Override
  public String encrypt(String password, String salt, int factor) {
    try {
        Charset Charset = StandardCharsets.US_ASCII;
        String urlSafeSalt = salt.replace('+', '-').replace('/', '_'); // because Stytch exports the latter type of Base 64, and Java/FusionAuth require the former
        byte[] hashedBytes = SCrypt.scrypt(password.getBytes(Charset), urlSafeSalt.getBytes(Charset), N_CpuCost, R_MemoryCost_BlockSize, P_Parallelization, KeyLength);
        return new String(Base64.encodeBase64(hashedBytes)); // Return Base64 with + and / symbols, not - and _ symbols
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

}
```

This program takes a password and salt and returns a hash. It accepts and returns the `+` form of Base64, but changes it to `-` to work with Java internally. The `factor` parameter is ignored. Scrypt instead has multiple parameters set at the top of the file. If any of these differ from the ones you received in your encrypted CSV file header, you need to change them in the Java file.

There are two other files linked to this scrypt plugin that you shouldn't need to alter:

*   `fusionauth-contrib/Password Hashing Plugins/src/test/java/com/mycompany/fusionauth/plugins/ExampleStytchScryptPasswordEncryptorTest.java`, which holds unit tests for the hasher.
*   `fusionauth-contrib/Password Hashing Plugins/src/main/java/com/mycompany/fusionauth/plugins/guice/MyExampleFusionAuthPluginModule.java`, which makes the hasher file known to FusionAuth.

If you make another hash plugin, you will need to make your own test and add your plugin to the Guice file.

Build the Java plugin and add it to FusionAuth:

*   Open a terminal and run the code below to build a Docker container for Java, with the repository shared as a volume into the container.
    
    ```
    cd fusionauth-contrib/.devcontainer
    docker build -t javaimage .
    cd ..
    docker run -it --name javabox -v .:/workspace javaimage
    ```
    
*   In the Docker container terminal that is now running, run the code below.
    
    ```
    cd "/workspace/Password Hashing Plugins"
    mvn clean install
    exit
    ```
    
*   If you have Java installed locally, you can run the Maven command without Docker.

All tests should pass, and the plugin file should be available in `Password Hashing Plugins/target/fusionauth-example-password-encryptor-0.1.0.jar`.

### Set Up FusionAuth And Deploy The Plugin[#](#set-up-fusionauth-and-deploy-the-plugin)

Now copy `fusionauth-example-password-encryptor-0.1.0.jar` to your FusionAuth `plugins` directory and restart FusionAuth.

If you are not already running FusionAuth or want to test this process on another instance, you can start FusionAuth in Docker.

*   Open a new terminal in the `fusionauth-import-scripts` directory and run the code below.
    
    ```
    cd stytch/fusionAuthDockerFiles
    docker compose up
    ```
    
*   FusionAuth will now be running and browsable at `http://localhost:9011`. You can log in with `admin@example.com` and `password`. The container is called **fa**.
*   Open a terminal in the `fusionauth-contrib` root directory, where you built the plugin. Run the commands below to copy the JAR file into the FusionAuth container `plugins` directory.
    
    ```
    docker exec fa mkdir /usr/local/fusionauth/plugins
    docker cp "Password Hashing Plugins/target/fusionauth-example-password-encryptor-0.1.0.jar" fa:/usr/local/fusionauth/plugins/fusionauth-example-password-encryptor-0.1.0.jar
    ```
    
*   Finally, restart FusionAuth for it to detect the plugin. In the terminal where FusionAuth is running in Docker, press `control-C` to stop it, wait, and run `docker compose up` again.

### Save The User Details And Hash To FusionAuth[#](#save-the-user-details-and-hash-to-fusionauth)

Now you have the users file `users.json` and the scrypt plugin is installed. To import the users into FusionAuth, you need to run the Node.js import script.

### Use the Script[#](#use-the-script)

Open a terminal in the `fusionauth-import-scripts` directory and run the code below.

```
cd stytch/js
npm update
node 4_import.mjs
```

This import script needs only FusionAuth to be running locally on port 9011 and the `users.json` file to exist in the `fusionauth-import-scripts/stytch/js` directory. The FusionAuth Kickstart file already set a sample application to match the Ids in `4_import.mjs`. If your FusionAuth installation is different, please edit the Ids in `4_import.js`.

You might want to set the user's registration and tenant Id in your production migration.

```
faUser.registrations = Array<UserRegistration>;
faUser.tenantId = UUID;
```

The `4_import.mjs` script is the code you will need to spend the most time editing if you want to customize your own migration. Let's look at how it works.

The script has two dependencies:

*   `stream-json` — A JSON library that can read massive files with millions of users incrementally. It opens `users.json` for reading in the line `new Chain([fs.createReadStream(filename), parser(), new StreamArray(),])`. For more information, read [https://github.com/uhop/stream-json](https://github.com/uhop/stream-json).
*   `@fusionauth/typescript-client` — The FusionAuth SDK for Node.js. It's only used for a single operation: `fa.importUsers(importRequest);`. For more information, read the [FusionAuth Typescript Client Library](/docs/sdks/typescript) documentation.

The main program loop is the code below.

```
for await (const { value: stytchUser } of stytchUsers) {
  const faUser = getFaUserFromStytchUser(stytchUser);
  await importUser(faUser, stytchUser);
}
```

The program takes a Stytch user from the JSON file, maps it to a FusionAuth user ready for import, and sends it to FusionAuth.

The `getFaUserFromStytchUser()` function does a few things:

*   Maps as many matching fields from Stytch to FusionAuth as possible.
*   Takes the first verified contact it can find or the first contact as the primary FusionAuth contact and saves additional contacts in the generic `data` **JSON** field. This is because FusionAuth allows only one email and phone number while Stytch has an array.
*   Stores all Stytch user details that don't map to FusionAuth in the **data** field.
*   Ignores empty fields and arrays.
*   Uses the name we registered for the hashing algorithm in the Java plugin in `faUser.encryptionScheme = 'example-salted-stytch-scrypt';`.

Carefully read this function and make sure that the user information you need is imported correctly. If any information is not needed, you can comment out the related lines.

The function `getNullOrUUIDFromUserId()` extracts a standard UUID from the Stytch format to set a user Id in FusionAuth.

Finally, this script imports users individually. If this is too slow when running the production migration, wrap the `importUsers()` FusionAuth SDK call in a loop that bundles users in batches of 1000.

If you are uncertain what a user attribute in FusionAuth does, read more in the [user guide](/docs/apis/users), as linked in the [general migration guide](/docs/lifecycle/migrate-users) recommended earlier.

### Import Roles[#](#import-roles)

Stytch does not support user roles and applications as native properties of users. Instead, you can [implement roles](https://stytch.com/docs/guides/authorization/rbac) by adding them as values in the JSON **trusted\_metadata** field of the user. This field is similar to the FusionAuth **data** field.

As discussed in the general migration guide, roles in FusionAuth are stored in a Registration object (a link between a User and an Application).

If you have roles in Stytch you want to import, add them to the `faUser.registrations` array in the import script. This is not included in the tutorial script because every Stytch client will have their own way of linking users and applications in Stytch using JSON.

### Verify the Import[#](#verify-the-import)

If the migration script ran successfully, you should be able to log in to FusionAuth with one of the imported users:

*   Browse to `http://localhost:9011`.
*   Enter username `user1@example.com` and password `averylongandunguessablepasswordwithlotsofrandominfooofisjoafasnr;,n1`.
*   Logging in should work, but your user will not be able to see anything as it has no administration rights.
*   Log out again and log in with `admin@example.com`.
*   Browse to Users and edit one of the imported users.
*   In the Source tab, you can see all the user details.

#### Debug With The FusionAuth Database[#](#debug-with-the-fusionauth-database)

If you have errors logging in, use the FusionAuth database directly to see if your users were imported, and check their hashes manually.

You can use any PostgreSQL browser. [DBeaver](https://dbeaver.io/download) is free, cross-platform, and open source. The connection details are in `fusionauth-import-scripts/stytch/fusionAuthDockerFiles/docker-compose.yml` and `.env`.

In DBeaver or your database IDE, create a new PostgreSQL connection with the following details:

*   **URL**: `jdbc:postgresql://localhost:6432/fusionauth`
*   **Host**: `localhost`
*   **Port**: `6432`
*   **Database**: `fusionauth`
*   **Username**: `fusionauth`
*   **Password**: `hkaLBM3RVnyYeYeqE3WI1w2e4Avpy0Wd5O3s3`

Log in to the database and browse to `Databases/fusionauth/Schemas/public/Tables/identities` and `users`. These two tables will show you the login credentials and user personal information.

## What to Do Next[#](#what-to-do-next)

You now have your users migrated, or a plan to do so. Congratulations! What is next?

You need to migrate additional configurations, as mentioned in [Other Entities](#other-entities). Since the type of configuration varies, it is hard to provide a full list of how to import these items, but the general pattern will be:

*   Identify corresponding FusionAuth functionality.
*   Configure it in your FusionAuth instance, either manually or by scripting it using the [client libraries](/docs/sdks/) or [API](/docs/apis/).
*   Update your application configuration to use the new FusionAuth functionality.

Make sure you assign your users to the appropriate FusionAuth applications. You can do this either:

*   As part of your import process by adding registrations at import time.
*   After users have been migrated with the [Registrations API](/docs/apis/registrations).

You'll also need to modify and test each of your applications, whether custom, open source, or commercial, to ensure:

*   Users can successfully log in.
*   The authorization code redirect is handled correctly.
*   Users receive appropriate permissions and roles based on the JWT.
*   The look and feel of the hosted login pages matches each application's look and feel.

If your application uses a standard OAuth, SAML or OIDC library to communicate with , the transition should be relatively painless.

## Additional Support[#](#additional-support)

If you need support in your migration beyond that provided in this guide, you may:

*   Post in our [forums](/community/forum/) and get support from the community.
*   [Purchase a support plan](/pricing) and get expert help from the Engineering Team.