Blog & article mapping
StagingBlog represents one article plus the blog it belongs to.
There’s no separate “blog” resource — every staging row migrates one
article, and Graftport creates the blog container automatically the
first time it sees a new blogName + blogSlug pair.
Quick reference — minimum loadable article
{
"original_id": post_id,
"blogName": "News",
"blogSlug": "news",
"title": title,
"body": content_html,
"authorName": author,
"publishDate": published_at
}Blog reference
Pick exactly one of these two strategies — by name (creates if missing) or by ID (assumes the blog exists).
blogNamestringThe blog’s display name. Graftport creates the blog container on
first sight of a new blogName + blogSlug pair, then reuses it
for subsequent articles.
"blogName": "News"blogSlugstringURL slug for the blog (the bit after /blogs/). Used together with
blogName to identify or create the blog.
"blogSlug": "news"blogIdstringShopify blog GID, if you already know the destination blog’s ID.
Setting blogId skips the create-blog logic — Graftport posts the
article directly into the existing blog.
Use this when the merchant already has a Shopify blog set up and you’re migrating articles into it.
Article content
titlestringrequiredArticle headline.
"title": titlehandlestringURL slug for the article. Auto-derived from title if omitted.
"handle": url_keybodystringThe article body, as HTML. Convert from Markdown / plain text in the mapping if your source uses something else.
"body": content_htmlsummarystringExcerpt shown in blog index pages.
"summary": short_descriptionauthorNamestringAuthor display name. Just a string — there’s no user record to link
to. Capture rich author data (email, bio) in dict_metafields if
the merchant needs it.
"authorName": author_name ? author_name : "Editor"publishDatestring (ISO8601)When the article was first published on the source platform. Drives the displayed date — always emit it from the source for accurate timeline preservation.
"publishDate": published_atisPublishedbooleandefault trueWhether the article is publicly visible. false creates the
article in draft state — exists in admin but not on the storefront.
"isPublished": status = "published"tagsstring[]Article tags. Used by the storefront for filtering.
"tags": tag_listFeatured image
One image per article. Different shape from product images — uses image_src + image_alt, not files.
image_srcstringURL of the featured image.
"image_src": featured_image_urlimage_altstringAlt text. Defaults to the article title if omitted (set explicitly for better accessibility).
Patterns
Magento CMS post → Shopify article
{
"original_id": post_id,
"blogName": "Stories",
"blogSlug": "stories",
"title": title,
"handle": identifier,
"body": content,
"summary": short_content,
"authorName": author_name ? author_name : "Editor",
"publishDate": publish_time,
"isPublished": is_active = "1",
"image_src": featured_image,
"image_alt": title,
"tags": tags
}Many posts under one blog (most common)
If every post in your source belongs to one blog, hard-code the blog fields:
{
"original_id": post_id,
"blogName": "News",
"blogSlug": "news",
"title": title,
"body": body_html,
"authorName": author,
"publishDate": created_at
}The first article creates the news blog; every subsequent article
attaches to it.
Multiple source sections → multiple blogs
{
"original_id": post_id,
"blogName": section.name,
"blogSlug": section.slug,
"title": title,
…
}Each unique (blogName, blogSlug) pair becomes its own Shopify
blog.
Posting into an existing Shopify blog
{
"original_id": post_id,
"blogId": "gid://shopify/Blog/123456789",
"title": title,
"body": body,
"authorName": author,
"publishDate": published_at
}Set blogId and skip blogName / blogSlug. Useful when the
merchant has already set up a custom blog with a specific theme
template.
Capturing rich author data as metafields
{
"original_id": post_id,
"blogName": "News",
"blogSlug": "news",
"title": title,
"body": body,
"authorName": author.name,
"publishDate": published_at,
"dict_metafields": {
"author_email": author.email,
"author_bio": author.bio,
"author_avatar": author.avatar_url
}
}Gotchas
One staging row = one article. If you have N articles, your mapping should produce N output rows. Don’t try to bundle multiple articles into a single staging row.
bodymust be HTML. Markdown won’t render — convert in the mapping (or pre-convert in the source extract) before emitting.- Inline images stay as
<img>tags inside the body. Only the featured image goes inimage_src. Inline image URLs need to resolve from the destination shop — the merchant may need to rehost old image URLs after migration. publishDatecontrols the displayed date, not the load time. Always emit it for historical accuracy.authorNameis just a string. No user account is created.- Theme migration is out of scope. This resource moves blog content, not the theme that displays it. The destination Shopify store needs a theme with blog templates already configured.
- Blog dedup: Graftport identifies a blog by its
(blogName, blogSlug)pair on first sight. Changing either between articles in the same migration creates a second blog. Be consistent. - Article handles must be unique within a blog, not across
blogs.
summer-salein/blogs/news/and/blogs/seasonal/are fine.