Migrating a page

Posted in October 18, 2021

In this tutorial, we are going to cover how to migrate the pages within our WordPress site to the Headless CMS Storyblok.

1st step. Create Content Type: page

The first step of the migration will be to create the Content Types in Storyblok, equivalent to the ones we have in our WordPress admin panel.

Basically, we need to replicate the schema, that WordPress has already defined, in our Storyblok space.

Go to the REST API documentation to check the reference pages schema.

Looking at the schema reference, you will need to check the fields you want to migrate to Storyblok. Not all fields will be needed, only the ones that have useful content for your site, like:

  • date: The date the object was published, in the site’s timezone (remember to set the timezone to the same one in your Storyblok space).

  • title: The title for the object.

  • slug: An alphanumeric identifier for the object unique to its type.

  • featured_image: The ID of the featured media for the object (the wordpress-importer to Storyblok plugin will import the image for you).

  • blocks: The content of the page (field to be displayed by adding REST API blocks to your WP).

For this example, it is important to note that when we are creating a Content Type in Storyblok, by default it comes with automatic fields such as first_published_at, name, slug, etc.

To facilitate the migration and not duplicate fields, we will reuse them in the mapping step.

Now that we are clear on the WP pages schema, we need to define the schema for the new page Content Type in Storyblok:

  • date will be the default field: first_published_at.

  • title will be the default field: name.

  • slug will be the default field: slug.

  • featured_image will be a new field of type: Assets > Images.

  • blocks will be a new field, named body, of type: Blocks (each of the Gutenberg sections/blocks will be defined as nested components in Storyblok).

2nd step. Create the migration script

Prerequisites

WP version > 4.7.0 (public release of REST API).

REST JSON API publicly available. Have access to /wp-json.

WP Permalinks selected by the name of the entry and not as plain (to maintain parent-child pages relationship).

Node ≥ 14.

Once we have all the prerequisites ready to start working on the migration, it is time to clone the plugin repository that will be in charge of the migration, and start defining the script that we will run for the migration.

The steps to follow to create the script are:

  • Clone the wordpress-importer plugin: git clone <https://github.com/storyblok/wordpress-importer.git>.

  • Install the NPM dependencies: npm install

  • Create a migrate-wp-to-storyblok.js file in the root of the project.

  • Add a command in the package.json to run the script when finished: "migrate": "node ./migrate-wp-to-storyblok.js"

Now that we have it on our computer, the next thing to do is to connect it to our Storyblok space and read the WP content from its API. To do this:

  • Copy/Paste the initial code in the .js file:

    import { Wp2Storyblok } from './index.js'

    const wp2storyblok = new Wp2Storyblok('[YOUR-REST-API-URL]', { token: [YOUR-STORYBLOK-TOKEN], // My Account > Personal access tokens space_id: [YOUR-STORYBLOK-SPACE-ID], // Settings blocks_mapping: [], content_types: [], })

    wp2storyblok.migrate()

  • Replace the [YOUR-*] by:

    • [YOUR-REST-API-URL]: Your REST API publicly available (Ex: https://wp2.storyblok.com/wp-json)

    • [YOUR-STORYBLOK-TOKEN]: OAuth token in the Storyblok space (My Account > Personal access tokens)

    • [YOUR-STORYBLOK-SPACE-ID]: Space_id in the Storyblok space (Settings > General)

You already have what you need to connect to your Storyblok space, so we can start defining the relationship between the WP schema and the Storyblok schema in the content_types array.

3rd step. Map the defined schemas

To do the mapping you first need to know how a content_type object is formed in the script and the field types it allows defining:

  • The basic object looks like:

    { name: 'pages', // Name of the post type in WP new_content_type: 'page', // Name of the Content Type in Storyblok folder: '', // Optional - Inside which folder you want to migrate the content schema_mapping: {} // Relation between WP field and Storyblok field },

  • The type of fields you can add are:

    • simple field, such as title.

    • sub-property of a field, like in this case the URL of the feature image.

    • A field migrated to a nested block in Storyblok (object format).

    // SIMPLE field "title": "name", // "Field in WP": "Field in Storyblok"

    // SUB-PROPERTY field "_links.wp:featuredmedia.0": "content.preview_image",

    // NESTED BLOCK field "content": { field: 'content.body_items', // Field name in Storyblok component: "rich-text", // Component name inside the above field component_field: "content" // Field name inside the component you are migrating to }

Now we have to identify what type each field is and form our object. This would be the result:

{
      name: 'pages',
      new_content_type: 'page',
      folder: '',
      schema_mapping: {
        date: 'first_published_at', // SIMPLE
        title: 'name', // SIMPLE
        slug: 'slug', // SIMPLE
        '_links.wp:featuredmedia.0': 'content.featured_image', // SUB-PROPERTY
        blocks: 'content.body', // SIMPLE WP : SUB-PROPERTY Storyblok
      }
},

For any field that we have created ourselves, and is not a default field, in the Content Type in Storyblok, we have to add ‘content.’ before the field while mapping.

And finally, it would be necessary to specify the types of blocks that are usually used from Gutenberg in the type of pages.

For example, imagine that you have created a heading block with level 2, it is normal that the plugin installed to obtain the blocks represents it as core/heading with an attrs object with its content and its level. The desired mapping would look like this:

blocks_mapping: [
  {
    name: 'core/heading',
    new_block_name: 'core-heading',
    schema_mapping: {
	'attrs.level': 'level',
	'attrs.content': 'content'
    }
  }
],

One thing that will surprise you is that, even if you don’t create the component in Storyblok for core-heading, the migration plugin will still migrate the content.

And from the panel, you can choose which properties you want and which you don’t, and it will automatically create the component for you.

Once we do the same exercise for all the components used in WP, the final result of the script will look like this:

import { Wp2Storyblok } from './index.js'

const wp2storyblok = new Wp2Storyblok('[YOUR-REST-API-URL]', {
  token: [YOUR-STORYBLOK-TOKEN],
  space_id: [YOUR-STORYBLOK-SPACE-ID],
  blocks_mapping: [
    {
      name: 'core/heading',
      new_block_name: 'core-heading',
      schema_mapping: {
        'attrs.level': 'level',
        'attrs.content': 'content'
      }
    },
    {
      name: 'core/paragraph',
      new_block_name: 'core-paragraph',
      schema_mapping: {
        'attrs.content': 'content'
      }
    },
    {
      name: 'core/quote',
      new_block_name: 'core-quote',
      schema_mapping: {
        'attrs.value': 'content'
      }
    },
		{
      name: 'core/image',
      new_block_name: 'core-image',
      schema_mapping: {
        'attrs.url': 'image'
      }
    }
  ],
  content_types: [
    {
      name: 'pages',
      new_content_type: 'page',
      folder: '',
      schema_mapping: {
        date: 'first_published_at',
        title: 'name',
        slug: 'slug',
        '_links.wp:featuredmedia.0': 'content.featured_image',
        blocks: 'content.body',
      }
    },
  ],
})

wp2storyblok.migrate()

Posted in Migration Page