⚠️ End-of-life for our legacy application will be February 1st 2020

Introduction

This document mainly concerns all of our users that are still actively using GraphCMS Legacy. We are reaching out to all legacy users via email and an in-app banner to make sure you have enough time to prepare the switch.

GraphCMS is offering internal migration tools to clone the existing legacy project into our new system. This includes the schema, as well as the content of the project.

Since there are minor API changes, we will use this documentation to make your aware of what you might need to change on your queries to fully leverage our new system.

Please read this document carefully and reach out to us for any additional questions!

Migration Path

We offer a migration path for all users of our legacy system. We will be using internal tools to copy over your project into the new system, allowing your to slowly transition from the old system without downtime.

You want to have your legacy project migrated?
Reach out to us via our chat in the bottom right corner or email support@graphcms.com.
Please always include your project name and ID (visible in the URL when having the project opened).

Due to the expected amount of migration requests we can only clone your existing project once, meaning any content that changes in the legacy project during migration will not be brought over.

If your legacy project is currently on a paid plan, you will need to purchase a new subscription in the new system and cancel the old subscription after the migration. We will gladly offer you a coupon to match the price of your old subscription and offer assistance if you need help.

Content Changes

There are some changes to content workflows in the new system which we will explain in the following section.

Publishing

The new system now replaces the old isPublished boolean field with a status enum that has the following values: PUBLISHED, DRAFT and ARCHIVED. Each content entry includes the status enum.

Localization / I18N

The way how localization works changes a bit in the new system. Our legacy system used relations to a "Translation" Model to model translations. The new system now does localization on a field level. Meaning localizing a field will create a single field for each language your project has set up. Find out more about Localization in the new system in our I18N documentation.

Rich Text Editor

There is a new Rich Text Editor in our new system, find out how to use it here. Legacy Rich Text field will be migrated into a Markdown Field in the new system. Documentation for all other available fields can be viewed here.

Webhooks

Webhooks in the new system are now global. Every content create, update or delete sends out a webhook, find out how to set them up here.

Further Reading

Some other new things we introduced in the new system include Staging, Content Views, Connect Views, Custom Roles, Project Cloning, Project Backups and the Import/Export API.

API Changes

The API in our new system slightly differs from the legacy API. The main changes will be discussed in the following topics.

The underlying technology of GraphCMS received a fantastic update and now the previous Relay and Simple API have been merged into one. One end point, many possibilities!

The new system currently doesn't offer model- or field-based CRUD permissions!

Pre-Applied Filters

The new API allows you now to set filters on your API with which you can restrict your API to only return published entries. See the docs for pre-applied filters. These filters can also be set for Permanent Auth Tokens.

Queries

The API will be a little different from what you are used in the current version. The all-Prefix from the queries is gone now. So a simple Query might look like this:

# Legacy API
query {
  allPosts {
    id
    title
    content
  }
}
# New API
query {
  posts {
    id
    title
    content
  }
}

Filtering

If you want to filter you now need to use the where argument:

# Legacy API
query {
  allAuthors(filter: { age_gt: 18 }) {
    id
    title
    content
  }
}
# New API
query {
  authors(where: { age_gt: 18 }) {
    id
    name
  }
}

This will be familiar to may developers who've spent time writing SQL queries.

A more advanced example:

# Legacy API
query {
  posts(filter: { title_in: ["My biggest Adventure", "My latest Hobbies"] }) {
    id
    title
    status
  }
}
# New API
query {
  posts(where: { title_in: ["My biggest Adventure", "My latest Hobbies"] }) {
    id
    title
    status
  }
}

and as before, you can chain boolean operators AND and OR:

# Legacy API
query {
  allPosts(
    filter: {
      AND: [
        { title_in: ["My biggest Adventure", "My latest Hobbies"] }
        { isPublished: true }
      ]
    }
  ) {
    id
    title
    isPublished
  }
}
# New API
query {
  posts(
    where: {
      AND: [
        { title_in: ["My biggest Adventure", "My latest Hobbies"] }
        { status: PUBLISHED }
      ]
    }
  ) {
    id
    title
    status
  }
}

Ordering

An example of ordering the response, the same as previous

query {
  posts(orderBy: title_ASC) {
    id
    title
    status
  }
}

Pagination

An example of a paginating query.

# New API
query {
  posts(first: 2, skip: 1) {
    id
    title
  }
}
# New API
query {
  posts(first: 2, after: "cixnen24p33lo0143bexvr52n") {
    id
    title
  }
}

Translations

In our legacy system Translation were queried via the relational syntax:

# Legacy API
{
  allPosts {
    translations {
      language
      postTitle
      postContent
    }
  }
}

The new system handles localization on a field level:

# New API
{
  posts {
    id
    title(locale: EN)
  }
}

Find out how to set up I18N and how to query translated content with params or headers.

Mutations

Mutations look different with the new syntax. The primary difference is the addition of a new data field in the body of the mutation. See below for examples.

Create Mutations

# Legacy API
mutation {
  createAuthor(age: 42, email: "zeus@example.com", name: "Zeus") {
    id
    name
  }
}
# New API
mutation {
  createAuthor(data: { age: 42, email: "zeus@example.com", name: "Zeus" }) {
    id
    name
  }
}

Update Mutations

Update takes both a data field AND a where field

# Legacy API
mutation {
  updateAuthor(email: "zeus2@example.com", name: "Zeus2") {
    id
    name
  }
}
# New API
mutation {
  updateAuthor(
    data: { email: "zeus2@example.com", name: "Zeus2" }
    where: { email: "zeus@example.com" }
  ) {
    id
    name
  }
}

Upsert Mutations (Update OR Insert)

When we want to either update an existing node, or create a new one in a single mutation, we can use upsert mutations. Previously this was accomplished with the little documented utility, updateOrCreate.

# New API
mutation {
  upsertAuthor(
    where: { email: "zeus@example.com" }
    create: { email: "zeus@example.com", age: 42, name: "Zeus" }
    update: { name: "Zeus" }
  ) {
    name
  }
}

Since this is not a very DRY example, a more common practice would be something like this:

# New API
mutation upsertAuthor($email: String!, $age: Int, $name: String) {
  upsertAuthor(
    where: { id: $email }
    create: { email: $email, age: $age, name: $name }
    update: { email: $email, age: $age, name: $name }
  ) {
    name
    id
    email
  }
}

Note, email here is a unique field on the model. For more about variables, look here.

Delete Mutations

Deleting builds on the usage of where but omits the data field.

# Legacy API
mutation {
  deleteAuthor(id: "12345") {
    id
    name
  }
}
# New API
mutation {
  deleteAuthor(where: { id: "cjcdi63l20adx0146vg20j1ck" }) {
    id
    name
    email
  }
}

Assets

When we are copying over your project into the new system, Assets will also be included. However, they will be re-uploaded, since it's a new project. This means that the handle, and thus the URL, changes. URL transformations are unaffected as we still use the same Asset Provider.

If you are using any hardcoded Asset URL in your applications, make sure to replace them with the new ones.

Since content entries and assets in the new system inherit the same ID from legacy, you can use it to search for the new URL:

  1. Locate a hardcoded asset URL like media.graphcms.com/iiesjfhg3bg5dfrfdr. The last part of the URL is called handle.
  2. Open the API Explorer in our legacy application (legacy.graphcms.com) and use the following query:
# Legacy API
{
  allAssets(filter: { handle: "iiesjfhg3bg5dfrfdr" }) {
    id
    handle
    url
  }
}
  1. The query will return the ID of the asset, which you can now use in our new systems API Explorer to fetch the new URL:
# New API
{
  asset(where: { id: "cj5usen0b1zdl01328bzvultw" }) {
    id
    handle
    url
  }
}
  1. You now have the new URL of the same asset and can replace it wherever you used it before!