Migrating to Prismic
Learn how to migrate your existing data to Prismic using our APIs.
Tools
Prismic comes with a set of tools to assist content migration.
APIs
- The Migration API allows for creating and updating documents in a Prismic repository.
- The Asset API allows for creating and updating assets in a Prismic repository.
Those APIs are consumed through @prismicio/client
, one of the packages assisting Prismic migration.
Packages
@prismicio/client
is the core Prismic package for creating web apps with Prismic and JavaScript.@prismicio/migrate
is a library of helpers to help you migrate content to Prismic.
Process
We recommend the following content migration process. It ensures all necessary pieces are in place at the right time.
1. Create a Prismic repository
Create a Prismic repository if you don’t have one yet. A Prismic repository stores a website’s content and assets.
Once created, configure the repository’s main locale and any additional locales needed for your project.
2. Set up a project and install Slice Machine
Follow the instructions displayed after creating the repository. These instructions guides you through setting up a project and installing Slice Machine, Prismic developer tool.
3. Define content models
Custom type and slice models describe the shape of your content. Writers will fill in fields defined by your content models, and the Document API will serve them to your website as JSON. With Prismic, content modeling happens within Slice Machine locally and models get stored in your project’s code.
This step is necessary for the Migration API to understand the shape of each document. It also comes with the benefit of generating TypeScript types for your content models, allowing @prismicio/client
to provide more accurate type hints.
Once content models are defined, make sure to push them from Slice Machine to Prismic.
4. Write a migration script
You will need to write a script to migrate content from your current website to Prismic. This is where the bulk of the migration process happens.
Because content might come from a variety of CMSs and in various forms, we cannot provide a universal migration script that would work for everyone, hence the need to write your own.
A typical migration script goes as follows:
- Fetch data from your current data sources.
- Convert that content to match your Prismic models.
- Create documents and assets in Prismic with
@prismicio/client
.
The following section gives you more guidance on writing migration scripts. You can also browse a complete example of a migration script on GitHub.
Migration Script
Script setup
Install @prismicio/client
, @prismicio/migrate
, and dotenv
.
npm install @prismicio/client @prismicio/migrate dotenv
Create a permanent write token and add it to a .env
file.
PRISMIC_WRITE_TOKEN=YOUR_TOKEN
Create a migrate.mjs
script at the root of your project with the following content:
import "dotenv/config";
import * as prismic from "@prismicio/client";
import { htmlAsRichText } from "@prismicio/migrate";
import { repositoryName } from "./slicemachine.config.json";
// Prismic setup
const writeClient = prismic.createWriteClient(
repositoryName,
{
writeToken: process.env.PRISMIC_WRITE_TOKEN,
},
);
const migration = prismic.createMigration();
// Custom migration code will go here...
// Execute the prepared migration at the very end of the script
await writeClient.migrate(migration, {
reporter: (event) => console.log(event),
});
The following examples show how to work with the migration
instance, before calling the writeClient.migrate()
method. How you write your script will depend on the shape of your existing content and your Prismic content models.
Creating documents
Create a new document in the migration with migration.createDocument()
. The document’s type
, uid
, lang
, and data
properties should be provided. A tags
property can optionally be provided. The document’s title should be provided as the second argument.
const document = migration.createDocument(
{
type: "page",
// For some document types, `uid` can be optional,
// TypeScript will let you know when it's the case.
uid: "home",
lang: "en-us",
tags: ["team-marketing", "team-branding"],
// Learn more in the "Providing document data" section.
data: {
/* ... */
},
},
"Homepage",
);
Creating alternate language documents
When working on a multi-language project, you likely have documents that are translations of one another (e.g. the “À propos” page is the French translation of the English “About Us” page). When a document is a translation of a master language document, you can link them together. This allows editors to quickly switch from one translation to another and to keep track of translation progress.
To register a link between a master language document and an alternate language document, use the masterLanguageDocument
option.
const enAboutUs = migration.createDocument(
{
type: "page",
uid: "about-us",
lang: "en-us",
data: {
/* ... */
},
},
"About Us",
);
const frAboutUs = migration.createDocument(
{
type: "page",
uid: "a-propos",
lang: "fr-fr",
data: {
/* ... */
},
},
"À propos",
// Learn more in the "Defining content relationships" section
{ masterLanguageDocument: enAboutUs },
);
Creating a document from Prismic
You can migrate content from one Prismic repository to another using migration.createDocumentFromPrismic()
. This method takes care of updating the document’s content relationships and creating its assets.
const otherClient = prismic.createClient(
"another-repository-name",
);
// Fetches a document from another repository
const documentFromPrismic = await otherClient.getByUID(
"page",
"home",
);
const document = migration.createDocumentFromPrismic(
documentFromPrismic,
"Homepage",
);
Updating documents
You can update existing documents using migration.updateDocument()
. The document’s uid
, tags
, and data
properties can be updated, as well as the title displayed in Prismic.
const documentToUpdate = await writeClient.getByUID(
"page",
"home",
);
// Add a tag
documentToUpdate.tags.push("team-marketing");
// Keep the same document title
const document = migration.updateDocument(documentToUpdate);
// Or update the document's title
const document = migration.updateDocument(
documentToUpdate,
"Updated title",
);
Creating assets
You can create assets using migration.createAsset()
. The asset’s file
and filename
should be provided. Metadata about the asset can be provided, including notes
, credits
, alt
, and tags
.
const asset = migration.createAsset(
"https://example.com/foo.png",
"foo.png",
// All metadata are optional
{
notes: "lorem",
credits: "ipsum",
alt: "dolor",
tags: ["sit", "amet"],
},
);
The file
parameter can be a URL string, a URL
object, a File
instance, or any value accepted by File
in your environment (e.g. a Base 64 string, Blob
, etc.). Any of the following will work:
// URL-like
migration.createAsset(
"https://example.com/foo.png",
"foo.png",
);
migration.createAsset(
new URL("https://example.com/bar.png"),
"bar.png",
);
// File-like
migration.createAsset(
new File(["example-data"]),
"baz.png",
);
migration.createAsset(
fs.readFileSync("quux.png"),
"quux.png",
);
Assets with the same source file are deduplicated. Metadata for each deduplicated asset will be merged. This becomes useful when defining asset fields.
const fooPng = migration.createAsset(
"https://example.com/foo.png",
"foo.png",
);
// Later in your code, but `foo.png` will only be created once.
const anotherFooPng = migration.createAsset(
"https://example.com/foo.png",
"foo.png",
);
Providing document data
When migrating documents, we need to create (or update) them with Prismic data. This is done through the data
property of documents created with migration.createDocument()
(or updated with migration.updateDocument()
).
The data
object gets fully typed thanks to TypeScript types generated by Slice Machine when custom types and slices are defined. From here, most Prismic fields can be defined as-is with data from your previous data sources.
const document = migration.createDocument({
type: 'page',
uid: 'home',
lang: 'en-us',
data: {
myBoolean: true,
myNumber: 3,
myKeyText: 'Lorem ipsum',
mySelect: 'primary',
myColor: '#663399',
myDate: '2024-05-23',
myTimestamp: '2024-05-23T12:34:56+123'
myLink: { link_type: 'Web', url: 'https://example.com' },
myEmbed: { embed_url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' },
myGeopoint: { latitude: 48.8673362731933 longitude: 2.37049508094787 },
myGroup: [{ /* First item */ }, { /* Second item... */ }],
mySliceZone: [{ /* First slice */ }, { /* Second slice... */ }],
},
}, 'Homepage')
To define rich text fields, asset-related fields, and content relationships (links between two documents), refer to the following sections.
Defining rich text fields
Basic usage
HTML content can be serialized to Prismic rich text with htmlAsRichText()
.
const title = htmlAsRichText("<h1>Hello World</h1>").result;
const body = htmlAsRichText(someBlogPostHTML).result;
const document = migration.createDocument(
{
type: "post",
uid: "hello-world",
lang: "en-us",
data: { title, body },
},
"Hello World",
);
htmlAsRichText()
supports an optional config
argument providing great flexibility to how HTML code gets converted. Learn more about it in its technical reference.
Images
Text images should be registered in your migration too as assets. This is done through the serializer
config.
const blogPostBody = htmlAsRichText(someBlogPostHTML, {
serializer: {
img: ({ node }) => {
const src = node.properties.src;
const filename = src.split("/").pop();
const alt = node.properties.alt;
return {
type: "image",
id: migration.createAsset(src, filename, { alt }),
};
},
},
}).result;
Content relationships
Text links to other Prismic documents should be declared as content relationships. This is also done through the serializer
config.
const blogPostBody = htmlAsRichText(someBlogPostHTML, {
serializer: {
a: ({ node }) => {
const href = node.properties.href;
// Matches URLs like `/blog/hello-world`
if (href.startsWith("/blog/")) {
// e.g. `hello-world`
const uid = href.split("/").pop();
return {
type: "hyperlink",
// Creates a content relationship to
// the blog post with a matching `uid`
data: () => migration.getByUID("blog", uid),
};
}
// Serializes other links as external links
return "hyperlink";
},
},
}).result;
Defining asset-related fields
Images
Image fields can be defined with their migration asset directly.
const document = migration.createDocument(
{
type: "post",
uid: "hello-world",
lang: "en-us",
data: {
thumbnail: migration.createAsset(
"https://example.com/foo.png",
"foo.png",
),
},
},
"Hello World",
);
If an image field was modeled with multiple thumbnails, it can be defined as an object with an id
property linking to the main image, and other properties defining additional thumbnails.
const fooPng = migration.createAsset(
"https://example.com/foo.png",
"foo.png",
);
const document = migration.createDocument(
{
type: "post",
uid: "hello-world",
lang: "en-us",
data: {
metaImage: {
id: fooPng,
squared: fooPng,
twitter: fooPng,
openGraph: fooPng,
},
},
},
"Hello World",
);
Links to media
Links to media fields can be defined as an object with an id
property linking to the linked asset.
const document = migration.createDocument(
{
type: "post",
uid: "hello-world",
lang: "en-us",
data: {
attachment: {
link_type: "Media",
id: migration.createAsset(
"https://example.com/foo.pdf",
"foo.pdf",
),
},
},
},
"Hello World",
);
Defining content relationships
Content relationships can be defined with migration documents. Migration documents are returned when creating (or updating) documents with migration.createDocument()
(or migration.updateDocument()
).
const fooDocument = migration.createDocument(
{
type: "page",
uid: "foo",
lang: "en-us",
data: {
/* ... */
},
},
"Foo",
);
const barDocument = migration.createDocument(
{
type: "page",
uid: "bar",
lang: "en-us",
data: {
related: fooDocument,
},
},
"Bar",
);
Content relationships can also be defined with an already existing document.
const existingBarDocument = await writeClient.getByUID(
"page",
"bar",
);
const fooDocument = migration.createDocument(
{
type: "page",
uid: "foo",
lang: "en-us",
data: {
related: existingBarDocument,
},
},
"Foo",
);
When facing circular content relationships (i.e. foo
links to bar
and bar
to foo
), we can define lazy content relationships with migration.getByUID()
and migration.getSingle()
.
const settingsDocument = migration.createDocument(
{
type: "settings", // `settings` is a singleton document with no `uid`
lang: "en-us",
data: {
related: () => migration.getByUID("page", "bar"),
},
},
"Settings",
);
const barDocument = migration.createDocument(
{
type: "page",
uid: "bar",
lang: "en-us",
data: {
related: () => migration.getSingle("settings"),
},
},
"Bar",
);
Lazy content relationships can be asynchronous, this allows for querying existing documents also for example.
const fooDocument = migration.createDocument(
{
type: "page",
uid: "foo",
lang: "en-us",
data: {
related: async () => {
const existingBarDocument =
await writeClient.getByUID("page", "bar");
return existingBarDocument;
},
},
},
"Foo",
);
Script execution
When ready, your script can be executed with the following command:
# For `migration.mjs`
node migrate.mjs
# For `migration.ts`
npx tsx migration.ts