# Migrate to Hygraph

Learn how to migrate your schema and content to Hygraph, including tools, best practices, and guidance for assets, rich text, and relational data.

Migrating to Hygraph involves two distinct phases: rebuilding your schema, then importing your content. Hygraph gives you the Management SDK and Content API to handle both programmatically, as well as a UI-based option for schema creation.

This guide covers the full migration flow, from exploring your existing data to importing content, along with tips for specific field types such as assets, rich text, and relations.

## Prerequisites

- An active Hygraph project
- A [Permanent Auth Token](https://hygraph.com/docs/api-reference/basics/authorization#permanent-auth-tokens) with Management API access (for schema creation via the SDK)
- A Permanent Auth Token with Content API mutations enabled (for content import)
- An export of your existing content in JSON or CSV format

## Step 1: Explore your current data

Before creating anything in Hygraph, examine the structure of your existing project and map it to Hygraph's data model.

![Plan your schema](https://hygraph.com/images/docs/getting-started/fundamentals/plan-your-schema.png)

Work through the following questions:

- What models and fields do you currently have, and how do they map to [Hygraph's field types](https://hygraph.com/docs/api-reference/schema/field-types)?
- How do your models relate to each other?
- Are there structures you want to normalize or improve as part of the migration?
- Which data belongs in Hygraph, and which belongs elsewhere? For example, you may want image assets in Hygraph but video assets in a dedicated streaming service.

Export your existing content to JSON or CSV so you can inspect field names, content types, and relations.

The examples in this guide use the following CSV of authors:

```csv
oldId,firstName,lastName
1,Stephen,King
2,Frank,Herbert
3,Brian,Herbert
4,Kevin,Anderson
5,Agatha,Christie
6,Haruki,Murakami
7,Isaac,Asimov
```

Hygraph supports over a dozen [field types](https://hygraph.com/docs/api-reference/schema/field-types), from strings, booleans, and dates to polymorphic union types and remote field resolvers. You get a small set of [system fields](https://hygraph.com/docs/api-reference/schema/system-fields) out of the box, but everything else is defined by you.

**On schema normalization:**
Some teams use migration as an opportunity to restructure their schema, for example, extracting repeated content into components. This can improve efficiency, but may require manual intervention when importing content. If you skip normalization for now, you can use `String` and `JSON` fields to represent most data without modification, though you will lose some filtering capability at the API level.

## Step 2: Create your schema

![Create your schema](https://hygraph.com/images/docs/getting-started/fundamentals/create-your-schema.png)

Create your schema before importing any content. You have two options: the Management SDK or the Hygraph UI. The SDK is faster for large or complex schemas and gives you a repeatable record of what was created.

### Use the Management SDK

The [Management SDK](https://hygraph.com/docs/api-reference/management-sdk/management-sdk) lets you create models, fields, enumerations, components, and remote sources programmatically. All changes are submitted as a single transaction. If any operation fails, the entire batch rolls back automatically.

**Install the SDK:**
```bash
npm install @hygraph/management-sdk
```

**Initialize the client:**

```ts
const { Client } = require('@hygraph/management-sdk');

// endpoint is your High Performance Content API URL
// found in Project Settings > Endpoints > High Performance Content API
const client = new Client({
  authToken,
  endpoint,
  name, // optional
});
```

Use `createModel` to create a model:
```js
client.createModel({
  apiId: '<your_api_id>',
  apiIdPlural: '<your_api_id_plural>',
  description: '<your_model_description>',
  displayName: '<Your model name>',
});
```

To create two models, `Author` and `Book`:
```js
client.createModel({
  apiId: 'Author',
  apiIdPlural: 'Authors',
  displayName: 'Author',
});

client.createModel({
  apiId: 'Book',
  apiIdPlural: 'Books',
  displayName: 'Book',
});
```

Use `createSimpleField` to add fields to a model. The example below shows all available options. Use only the ones you need:
```js
client.createSimpleField({
  apiId: '<your_api_id>',
  description: '<your_description>',
  displayName: '<your_display_name>',
  embeddableModels: '<embeddable_models>',
  embedsEnabled: '<boolean>',
  formConfig: '<form_config_json>',
  formExtension: '<form_extension>',
  formRenderer: '<form_renderer>',
  isHidden: '<boolean>',
  isList: '<boolean>',
  isLocalized: '<boolean>',
  isRequired: '<boolean>',
  isTitle: '<boolean>',
  isUnique: '<boolean>',
  migrationValue: '<migration_value>',
  parentApiId: '<parent_api_id>',
  position: '<int>',
  tableConfig: '<table_config_json>',
  tableExtension: '<table_extension>',
  tableRenderer: '<table_renderer>',
  type: SimpleFieldType.STRING,
  validations: '<SimpleFieldValidationsInput>',
  visibility: '<VisibilityTypes>',
});
```

To add a required string field to the `Author` model:
```js
client.createSimpleField({
  parentApiId: 'Author',
  type: SimpleFieldType.STRING,
  apiId: 'favoritePastime',
  displayName: 'Author Favorite Pastime',
  isRequired: true,
  visibility: VisibilityTypes.ReadWrite,
});
```

**Full migration example**

The script below creates an `Author` model with `firstName` and `lastName` fields:
```js
// migration.js
const { Client, SimpleFieldType } = require('@hygraph/management-sdk');

const client = new Client({
  authToken: '<your_permanent_auth_token>',
  endpoint: '<your_content_api_endpoint>',
});

// Create the Author model
client.createModel({
  apiId: 'Author',
  apiIdPlural: 'Authors',
  displayName: 'Author',
});

// Add firstName field to Author
client.createSimpleField({
  parentApiId: 'Author',
  apiId: 'firstName',
  displayName: 'First Name',
  type: SimpleFieldType.STRING,
});

// Add lastName field to Author
client.createSimpleField({
  parentApiId: 'Author',
  apiId: 'lastName',
  displayName: 'Last Name',
  type: SimpleFieldType.STRING,
});

// Preview all changes before committing
const changes = client.dryRun();
console.log(changes);
```

Run the script from the command line:
```bash
node migration.js
```

Review the `changes` array to confirm the operations that will be applied. Once you are satisfied, replace `dryRun()` with `run()` to commit the changes:
```js
async function runMigration() {
  const result = await client.run(true);

  if (result.errors) {
    throw new Error(result.errors);
  }

  console.log(result.name);
}

runMigration();
```

Once the migration runs, verify the result by checking the schema editor in Hygraph or [introspecting](https://graphql.org/learn/introspection/) your endpoint.

If your schema includes components, enumerations, or remote source fields, create those at the schema level before adding them to models. See the [Management SDK field creation examples](https://hygraph.com/docs/api-reference/management-sdk/management-sdk-field-examples) for instructions.

**Additional resources:**
- [Management SDK quickstart](https://hygraph.com/docs/api-reference/management-sdk/management-sdk-quickstart)
- [Management SDK methods reference](https://hygraph.com/docs/api-reference/management-sdk/management-sdk-methods-reference)
- [Management SDK full example](https://hygraph.com/docs/api-reference/management-sdk/management-sdk-example)
- [Batch migrations](https://hygraph.com/docs/api-reference/management-sdk/management-sdk-batchmigration)

### Use the UI

To create your schema in the Hygraph UI, navigate to the schema editor and create your models, then add fields to each one. The [getting started guide](https://hygraph.com/docs/getting-started/quickstart) covers creating models and adding fields.

## Step 3: Plan your content migration

With your schema in place, review your existing content and map it to the new structure before importing anything.

Order matters when content is relational. For example, you must create asset entries before creating content entries that reference them, otherwise the relation cannot be established at import time.

As you plan, identify:

- Which content needs to be migrated first to unblock dependent content
- Which content is critical vs. supporting, so you can prioritize accordingly
- Where the shape of your existing data differs from your new input types, and what transformation is needed

## Step 4: Import your content

Import assets before content entries. Relations cannot be established until the assets they reference exist. How you upload assets depends on which asset system your project uses.

![API Endpoints](https://hygraph.com/images/docs/getting-started/fundamentals/api-endpoints-location.png)

### Assets

Projects created after February 2024 use the Hygraph Asset Management system. Projects older than that use the Legacy asset system. The upload process differs between the two:

- **Hygraph Asset Management:** Asset uploads are part of the native GraphQL API. Upload through `createAsset` mutations instead.
- **Legacy asset system:** Uses a dedicated HTTP upload endpoint at `/upload` appended to your project URL.

To check which system your project uses, navigate to **Project Settings > Access > Endpoints** and look for an asset upload endpoint. If one is listed, your project uses the legacy system. If not, it uses Hygraph Asset Management.

You will need a [Permanent Auth Token](https://hygraph.com/docs/api-reference/basics/authorization#permanent-auth-tokens) with **Mutations** access enabled to upload assets.

**Warning:**
File size limits depend on your plan. [Check the pricing page](https://hygraph.com/pricing) for details.

Assets follow the same [environment](https://hygraph.com/docs/api-reference/basics/environments) and [authorization](https://hygraph.com/docs/api-reference/basics/authorization) settings as all other content in your project.

After uploading, [publish your assets](https://hygraph.com/docs/api-reference/assets/publishing-assets) before they can be served alongside published content. For full asset upload documentation, see [Uploading assets](https://hygraph.com/docs/api-reference/assets/uploading-assets).

**Upload by file — Hygraph Asset Management**

First, create the asset via mutation to receive the upload URL and credentials:

  **Mutation**
```graphql
mutation createAsset {
  createAsset(data: {}) {
    id
    url
    upload {
      status
      expiresAt
      error {
        code
        message
      }
      requestPostData {
        url
        date
        key
        signature
        algorithm
        policy
        credential
        securityToken
      }
    }
  }
}
```

  
  **Response**
```json
{
  "data": {
    "createAsset": {
      "id": "clt47n0t600j807vvirlzi1xx",
      "url": "https://eu-central-1.graphassets.com/clpqzrnm4007e01t810b59ir4/clt47n0t600j907vveibipmov",
      "upload": {
        "status": "ASSET_CREATE_PENDING",
        "expiresAt": "2024-02-27T12:38:49+00:00",
        "error": null,
        "requestPostData": {
          "url": "https://eu-1-assets-delivery-hg75hf.s3.eu-central-1.amazonaws.com",
          "date": "20240227T101349Z",
          "key": "clpqzrnm4007e01t810b59ir4/upload/...",
          "signature": "c17e7b1c5d4af665a8fc74421fae53b72e94bb19e85e7befd1eb79b865bef7d2",
          "algorithm": "AWS4-HMAC-SHA256",
          "policy": "eyJleHBpcmF0aW9uIjo...",
          "credential": "ASIAVQRE3VMEWGY5C2XL/20240227/eu-central-1/s3/aws4_request",
          "securityToken": "IQoJb3JpZ2luX2Vj..."
        }
      }
    }
  }
}
```


Then upload the file using the credentials from the response:
```bash
curl --request POST \
  --url $URL \
  --form X-Amz-Date=$DATE \
  --form key=$KEY \
  --form X-Amz-Signature=$SIGNATURE \
  --form X-Amz-Algorithm=$ALGORITHM \
  --form policy=$POLICY \
  --form X-Amz-Credential=$CREDENTIAL \
  --form X-Amz-Security-Token=$SECURITY_TOKEN \
  --form file=@./test.jpg
```

**Upload by remote URL — Hygraph Asset Management**
```graphql
mutation uploadByUrl {
  createAsset(
    data: {
      uploadUrl: "https://images.unsplash.com/photo-1682687218147-9806132dc697"
    }
  ) {
    id
    url
  }
}
```

**Upload by file — legacy asset system**

  **cURL**
```bash
curl -XPOST -H "Authorization: Bearer {YOUR_PAT_VALUE}" -F fileUpload=@picture.jpg https://[region].hygraph.com/v2/[projectId]/[environment]/upload
```

  
  **Node.js**
```js
// File must use the .mjs extension, as node-fetch is an ESM-only package.
import fetch, { FormData, fileFrom } from 'node-fetch';

const form = new FormData();

form.set('fileUpload', await fileFrom('path/to/file.png'));

fetch(`${process.env.HYGRAPH_URL}/upload`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HYGRAPH_ASSET_TOKEN}`,
  },
  body: form,
})
  .then((res) => res.json())
  .then((data) => console.log(JSON.stringify(data, null, 2)))
  .catch((err) => console.log(err));
```

  
  **JavaScript**
```js
const HYGRAPH_URL = '';
const HYGRAPH_ASSET_TOKEN = '';

async function upload() {
  const input = document.getElementById('fileUpload');
  const file = input.files[0];

  const form = new FormData();
  form.append('fileUpload', file);

  // Do not expose HYGRAPH_ASSET_TOKEN in front-end code in production.
  // Use a backend service to handle uploads and keep the token server-side.
  const response = await fetch(`${HYGRAPH_URL}/upload`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${HYGRAPH_ASSET_TOKEN}`,
    },
    body: form,
  });

  const data = await response.json();
  console.log(JSON.stringify(data, null, 2));
}
```

  
  **Response**
```json
{
  "filename": "pexels-photo-1170986.jpeg",
  "mimetype": "image/jpeg",
  "size": 32476,
  "width": 500,
  "height": 750,
  "url": "https://media.graphassets.com/P3TkBzxyQLupgDWNFydB",
  "id": "ckfdz530o0001ip92cdr3bbmj"
}
```


**Upload by remote URL — legacy asset system**

  **cURL**
```bash
curl -XPOST -d url=https://media.graphassets.com/P3TkBzxyQLupgDWNFydB https://[region].hygraph.com/v2/[projectId]/[environment]/upload
```

  
  **Node.js**
```js
// File must use the .mjs extension, as node-fetch is an ESM-only package.
import fetch from 'node-fetch';

fetch(`${process.env.HYGRAPH_URL}/upload`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HYGRAPH_ASSET_TOKEN}`,
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: `url=${encodeURIComponent(
    'https://media.graphassets.com/P3TkBzxyQLupgDWNFydB'
  )}`,
})
  .then((res) => res.json())
  .then((data) => console.log(JSON.stringify(data, null, 2)))
  .catch((err) => console.log(err));
```

  
  **Response**
```json
{
  "filename": "pexels-photo-1170986.jpeg",
  "mimetype": "image/jpeg",
  "size": 32476,
  "width": 500,
  "height": 750,
  "url": "https://media.graphassets.com/J9BOEF3OSuuSXDhvriQa",
  "id": "ckgs20b80017109547vfev24c"
}
```


### Content entries

To find your Content API endpoint, go to **Project Settings > Access > Endpoints > High Performance Content API**.

Use [GraphQL mutations](https://hygraph.com/docs/api-reference/content-api/mutations#auto-generated-mutations) to create content entries. Hygraph auto-generates mutations for every model you create. Because existing data rarely maps 1:1 to your new schema, you will likely need to transform your dataset to match your new input types before importing.

The script below shows a complete import example using the [CSV authors file from Step 1](#step-1-explore-your-current-data):
```js
// Import necessary libraries
const { GraphQLClient, gql } = require('graphql-request');
const csvToJson = require('csvtojson');

require('dotenv').config();

// Initialize GraphQL client
const client = new GraphQLClient(process.env.HYGRAPH_ENDPOINT, {
  headers: {
    authorization: `Bearer ${process.env.HYGRAPH_TOKEN}`,
  },
});

// Build a mutation from a data row
function createMutation(data) {
  return gql`
    mutation MyMutation {
      createAuthor(data: {
        firstName: "${data.firstName}",
        lastName: "${data.lastName}",
        oldId: "${data.oldId}"
      }) {
        id
      }
    }
  `;
}

// Run the migration
async function run() {
  // Load and parse the CSV
  const data = await csvToJson().fromFile('./data.csv');

  // Build mutations from each row
  const mutations = data.map((item) => createMutation(item));

  // Execute each mutation with a 1-second delay between requests
  mutations.forEach((mutation, index) => {
    setTimeout(() => {
      console.log(`Running mutation ${index + 1} of ${mutations.length}`);
      client.request(mutation).then((response) => {
        console.log(response);
      });
    }, (index + 1) * 1000);
  });
}

run();
```

See [rate limits](https://hygraph.com/docs/api-reference/basics/rate-limits) for guidance on request frequency.

### Rich text

Hygraph stores rich text as an Abstract Syntax Tree (AST) based on [Slate](https://docs.slatejs.org/). If your existing content stores rich text as HTML or another format, you need to convert it before importing.

1. **Convert:** Use [Hygraph's HTML-to-Slate AST converter](https://github.com/hygraph/rich-text/tree/main/packages/html-to-slate-ast) to transform your existing rich text into the correct AST format.
2. **Import:** Use a `create` mutation with a `RichTextAST` variable to import the converted content.

  **Mutation**
```graphql
mutation createArticle($title: String, $content: RichTextAST) {
  createArticle(data: {
    title: $title,
    content: $content
  }) {
    title
    content {
      raw
    }
  }
}
```

  
  **Variables**
```json
{
  "title": "Working with the Hygraph Rich Text field",
  "content": {
    "children": [
      {
        "type": "paragraph",
        "children": [
          {
            "text": "Hygraph boasts an impressive collection of "
          },
          {
            "href": "https://hygraph.com/docs/api-reference/schema/field-types",
            "type": "link",
            "children": [{ "text": "Field Types" }]
          },
          {
            "text": " that you can use when content modeling."
          }
        ]
      }
    ]
  }
}
```


For additional rich text utilities, see the [Hygraph rich text helpers](https://github.com/hygraph/rich-text).

### Relations

There are two approaches to migrating relational content.

**Option 1: Create with nested mutations**

Use a [create mutation](https://hygraph.com/docs/api-reference/content-api/mutations#create-entries) with a nested create to build both sides of a relation in one request. For subsequent entries that share the same related record, use a `connect` mutation instead of creating a duplicate.

**Option 2: Create separately, then connect**

Create all entries for each model first using [create mutations](https://hygraph.com/docs/api-reference/content-api/mutations#create-entries), then wire them together using [update mutations](https://hygraph.com/docs/api-reference/content-api/mutations#update-entries) or [update many mutations](https://hygraph.com/docs/api-reference/content-api/mutations#update-many). This approach is more straightforward but requires more total mutations.

**Warning:**
Option 2 requires more mutations to be sent. Review the [rate limits](https://hygraph.com/docs/api-reference/basics/rate-limits) documentation before choosing your approach.

  **create**
```graphql
# Create a book with a new author (one relation)
mutation createOneRelation {
  createBook(
    data: {
      name: "Rose madder"
      slug: "rose-madder"
      price: 30
      author: { create: { name: "Stephen King", slug: "stephen-king" } }
    }
  ) {
    id
    name
    author {
      name
    }
  }
}

# Create a book with multiple new authors
mutation createManyRelations {
  createBook(
    data: {
      name: "The road to Dune"
      slug: "the-road-to-dune"
      price: 30
      authors: {
        create: [
          { name: "Frank Herbert", slug: "frank-herbert" }
          { name: "Brian Herbert", slug: "brian-herbert" }
          { name: "Kevin Anderson", slug: "kevin-anderson" }
        ]
      }
    }
  ) {
    id
    name
    author {
      name
    }
  }
}
```

  
  **connect**
```graphql
# Create a book and connect to an existing author
mutation createAndConnectOne {
  createBook(
    data: {
      name: "Rose madder"
      slug: "rose-madder"
      price: 30
      author: { connect: { slug: "stephen-king" } }
    }
  ) {
    id
    name
    author {
      name
    }
  }
}

# Create a book and connect to multiple existing authors
mutation createAndConnectMany {
  createBook(
    data: {
      name: "The road to Dune"
      slug: "the-road-to-dune"
      price: 30
      authors: {
        connect: [
          { slug: "frank-herbert" }
          { slug: "brian-herbert" }
          { slug: "kevin-anderson" }
        ]
      }
    }
  ) {
    id
    name
    author {
      name
    }
  }
}
```


## Migration best practices

Follow these guidelines to keep your migration predictable and recoverable:

- Plan the full migration before you start. Map your existing schema to Hygraph models and fields, and identify any transformations needed.
- Use the migration as an opportunity to improve your schema. Hygraph features like components can reduce duplication. Restructure where it makes sense.
- Migrate in dependency order. Assets must exist before content entries that reference them. Shared models must exist before models that connect to them.
- Prioritize critical content. Identify your most important content and migrate it first, before supporting or supplementary content.
- Avoid large, complex mutations. If a model connects to many other models, do not try to create and connect everything in a single mutation. Space requests out and stay within [rate limits](https://hygraph.com/docs/api-reference/basics/rate-limits).

## What's next

- [Management SDK](https://hygraph.com/docs/api-reference/management-sdk/management-sdk): Full reference for creating and updating schema elements programmatically.
- [Content API mutations](https://hygraph.com/docs/api-reference/content-api/mutations): Reference for create, update, connect, and nested mutations.
- [Upload assets](https://hygraph.com/docs/api-reference/assets/uploading-assets): Full documentation for asset uploads, including both legacy and current asset systems.
- [Rate limits](https://hygraph.com/docs/api-reference/basics/rate-limits): Understand request limits before running large-scale imports.
- [Field types](https://hygraph.com/docs/api-reference/schema/field-types): Reference for all field types available in Hygraph.
