Migration notes
Notes to myself, because migrations are a thing I do
I'm capturing some notes from migrations, as the documentation doesn't quite cover any of the complexities of doing a real-life migration to Ghost, from a variety of platforms. The way this usually goes, I'll find this post while searching for something I'm stuck on, six months from now when I've forgotten it. But meanwhile, maybe it'll be useful to you, too!
What anyone doing a non-trivial migration should know:
Lots of command line tools, focused specifically on migrations
Not specifically for migration, but solves a lot of migration-related problems.
Many packages import Ghost content as mobiledoc. This is a problem if you need to interact with the content and are expecting lexical from the API. Here's how to convert it:
async function convertToLexical(postId, updatedAt) {
try {
// Construct the API URL with query parameter
const baseUrl = process.env.API_URL.replace(/\/$/, '');
const url = `${baseUrl}/ghost/api/admin/posts/${postId}?convert_to_lexical=true`;
// Prepare the request body with only updated_at
// Ghost Admin API expects { posts: [{ ... }] } format
const body = JSON.stringify({
posts: [{
id: postId,
updated_at: updatedAt
}]
});
// Generate JWT token for authentication
const token = generateGhostToken();
const authHeader = `Ghost ${token}`;
// Make the PUT request
const response = await fetch(url, {
method: 'PUT',
headers: {
'Authorization': authHeader,
'Content-Type': 'application/json'
},
body: body
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
console.log(`✓ Converted post ${postId} to lexical`);
return true;
} catch (error) {
console.error(`Error converting post ${postId}:`, error.message);
return false;
}
}Another option for converting between mobiledoc and lexical is hidden away in Ghost's Koenig package:
Super big thanks to Kevin on the Ghost dev team for pointing both of the conversion options above out. I was losing my mind.
More to come!
Hey, before you go... If your finances allow you to keep this tea-drinking ghost and the freelancer behind her supplied with our hot beverage of choice, we'd both appreciate it!