How to Migrate Nodes in Neos

Node Migrations are a crucial tool in Neos when it comes to managing the lifecycle of content. They allow us to make changes to existing node types and define transformations that can be applied to content, modifying it in accordance with the definition. In this blog post, I want to demonstrate how to use Neos Node Migrations to change a reference property on a set of node types.

Let's have a look at the problem at hand first. We've got a site with existing content. Some of it uses a mixin that allows us to define an internal reference to another node, using the reference property type.

'My.Website:TargetNodeMixin':
abstract: TRUE
properties:
'targetNode':
type: 'reference'
ui:
label: 'Target Node'

Our customer comes along and says "Hey, can we allow external links for all these nodes? We'd like to be able to link to our newest marketing microsite, which is hosted on another domain." Sure, we say, because all it takes is to change the property to a normal string and switch on the LinkEditor for that property.

'My.Website:TargetNodeMixin':
abstract: TRUE
properties:
'targetNode':
      type: string
ui:
inspector:
editor: 'TYPO3.Neos/Inspector/Editors/LinkEditor'


Or is it? Once we make that change, we notice something a little off with the existing links. Since we've changed the type from reference to string, Neos no longer knows how to resolve the existing links:

Now, do we really want to tell our customer that he needs to re-set all links on the entire site? Certainly not. Instead of changing the existing property (targetNode, with type reference), we create a new one, called target, and have a look at the DB to see what's what. We search for the node ID and find this:

{    
    "targetNode": "d8c4b1dc-8d2a-4998-b3a2-281bbce83499",
    "target": "node:\/\/d8c4b1dc-8d2a-4998-b3a2-281bbce83499"
}

We can see that while the content of the targetNode property (which is of type reference) is a simple node ID, Neos prefixed it with node:// in the new target property. That's how it distinguishes between external URLs and links to other nodes in properties of type string.

Creating the Node Migration

Now that we know what's the difference between our old and new content, it's time to create a migration. Our migration will search for all nodes of type My.Website:TargetNodeMixin and prepend the targetNode property with the string node://. The first step to building that node migration is creating the migration file. It's a YAML file, quite similar in structure to a Doctrine migration. The migration file goes in your package in the directory Migrations/TYPO3CR. In this example, I'm naming it Version20160822190330.yaml. Let's give it the following (empty) content for a start:

up:
comments: 'This is a demo migration!'
down:
comments: 'Demo goes down again, too!'

When we now execute ./flow node:migrationstatus, we should see our migration appearing at the bottom of the list. If it doesn't, something is wrong with your file path. Of course, this migration has no content yet and running it would result in an error. We need to tell Neos two things:

  • Which nodes to select to apply the migration - this is called a filter.
  • What to do with the selected nodes. This is called a transformation.

Let's add this to our migration file:

up:
warnings: 'Careful: do not run twice, because there is no easy way to detect multiple usages!'
comments: 'Migrates the targetNode property contained in the TargetNodeMixin from type reference to string by prepending node:// to all existing fields'
migration:
-
filters:
-
type: 'NodeType'
settings:
nodeType: 'My.Website:TargetNodeMixin'
withSubTypes: TRUE
-
type: 'PropertyNotEmpty'
settings:
propertyName: 'targetNode'
transformations:
-
type: 'ChangePropertyValue'
settings:
property: 'targetNode'
newValue: 'node://{current}'
down:
warnings: 'No down migration available'

Our migration now defines two filters, specifying which nodes to select. We want only nodes that have the type of our mixin, including children since our mixin is, of course, abstract. Also, we want only the ones that actually have some content in the property we will transform. The transformation tells Neos to prepend the string node:// to the current content of the property. We added a confirmation because we did not specify a way to undo our migration, so we want the user to know what they are doing.

Of course, the filters and transformations don't appear magically. Like most things in Neos, they are simple PHP classes. In this case, they're located in the package TYPO3.TYPO3CR in the paths Migration/Filters and Migration/Transformations. Have a look at the classes there to understand what's going on under the hood.

Running the Migration

Let's run our migration now! Execute the following command:

./flow node:migrate 20160822190330 --confirmation TRUE

if everything went well, we get a simple output:

Successfully applied migration.

That's it! We can now package the migration up with the node type changes (and possible changes to layout files) and deploy it to our existing site. Once the deployment is done, running the migration once will update all existing nodes to use the changed node type correctly.