Shopify
Limited · Tier 2 (head injection caveats)Best when your blog supports a product catalog. Shopify’s Admin API is solid, but blog templates don’t expose <head> the way modern CMSes do — JSON-LD ships inline in body, and head meta requires either a structured-data theme or Liquid edits. Read the limits before connecting.
<head> hooks for per-post JSON-LD. We work around this by injecting JSON-LD as the first element of body_html — Google parses it, but the “ideal” placement (head) requires a small Liquid theme edit (see “After publishing” below). Plan for the edit if AEO scoring matters to you.How it works
Shopify exposes blog posts via /admin/api/2024-10/blogs/{blog_id}/articles.json. We POST title, handle (Shopify’s slug), body HTML, image URL, tags (as a comma-separated string — Shopify’s API quirk), and meta tags via theglobal.title_tag / global.description_tag metafields that ship with every Shopify theme.
What we support
| Feature | Supported | Notes |
|---|---|---|
| Publish + update + unpublish | Yes | Full CRUD via /blogs/{id}/articles.json. |
| Native scheduling | Yes | published_at + published: false until time. Shopify cron handles flip. |
| Draft mode | Yes | published: false. Visible only in admin. |
| Featured image | Yes | image.src + alt — Shopify ingests + re-hosts on its CDN. |
| Tags | Yes | Comma-separated string (Shopify's only way to pass tags via API). |
| Author | Partial | Plain text field, not a reference. We send the author name; no author profile linkage. |
| Meta title + description | Yes | global.title_tag / global.description_tag metafields — read by every Shopify theme. |
| Canonical URL | — No | Shopify does not expose a per-post canonical via API. Set theme-wide canonical via Liquid. |
| JSON-LD in <head> | — No | Inline in body only. Liquid edit required for head placement (instructions below). |
| OpenGraph fields | Partial | Shopify auto-derives OG from title + summary + featured image. Custom OG image not exposed via API. |
| Multi-blog (multiple blogs in one store) | Yes | Connect each blog as its own SEORAV integration with the matching blog_id. |
| Articles + FAQ pages | Yes | Publishes not only blog articles but also FAQ / answer-engine pages. One connection per content type — typically point each at a different Shopify blog so the editorial surfaces stay separate. |
blog_idso articles and answers don’t share the same blog index.Setup
- 1
Create a Custom App
In Shopify admin: Settings → Apps and sales channels → Develop apps. Click Create an app, name it
SEORAV.Custom Apps replaced Private Apps in 2022. Functionality is identical; the access tokens have prefixshpat_. - 2
Configure Admin API scopes
Click Configure Admin API scopes and enable:
read_content,write_content— blog posts + pagesread_files,write_files— featured image upload
Save, then click Install app at the top right.
- 3
Reveal the access token
After install, Shopify shows the Admin API access token (starts with
shpat_). Copy it now — Shopify only shows this once. - 4
Find your Blog ID
Visit
https://yourstore.myshopify.com/admin/api/2024-10/blogs.jsonin your browser (you’ll need to be logged in). Find your blog and note the numericid. Most stores have one blog with id like87613014123. - 5
Connect inside SEORAV
Integrations → Connect → Shopify. Paste store URL, access token, and blog ID. Probe verifies scopes + blog reachability.
What we send to Shopify
POST https://yourstore.myshopify.com/admin/api/2024-10/blogs/{blog_id}/articles.json
X-Shopify-Access-Token: shpat_•••
Content-Type: application/json
{
"article": {
"title": "How to choose a reverse-osmosis system in 2026",
"handle": "how-to-choose-reverse-osmosis-system-2026",
"author": "Elena Vance",
"tags": "reverse osmosis, water filtration, buying guide",
"body_html":"<script type=\"application/ld+json\">…</script>\n<h2>What you actually need to know</h2><p>…</p>",
"summary_html": "Membrane stages, recovery rate, remineralisation — three specs that matter.",
"published": true,
"image": {
"src": "https://cdn.seorav.com/articles/9b1c5e0a/hero.webp",
"alt": "Three reverse-osmosis systems on a kitchen counter"
},
"metafields": [
{ "namespace":"global", "key":"title_tag", "value":"Reverse osmosis · the 3 specs that matter (2026)", "type":"single_line_text_field" },
{ "namespace":"global", "key":"description_tag", "value":"Membrane stages, recovery rate, remineralisation. …", "type":"single_line_text_field" }
]
}
}Field mapping
| Property | Type | Description |
|---|---|---|
| title, handle, body_html | string | Direct mapping. handle is Shopify's URL slug. |
| tags | string | Comma-separated. Shopify's API doesn't accept arrays for tags. |
| author | string | Plain text. No reference to a customer or staff profile. |
| image.src + image.alt | string | Hero URL + alt. Shopify downloads + re-hosts on its CDN automatically. |
| published | boolean | true / false. Combined with published_at for scheduling. |
| metafields[global.title_tag] | metafield | Sets the <title> tag on the rendered page (overrides the theme default). |
| metafields[global.description_tag] | metafield | Sets <meta name="description">. |
| jsonld_blocks (inline) | HTML | Joined as <script type="application/ld+json"> tags and prepended to body_html. |
Publishing options
Auto-publish
published: true with published_at set to now. Live immediately at /blogs/{blog-handle}/{article-handle}.
Scheduled
published: false + published_at: <ISO8601>. Shopify’s scheduler flips it automatically.
Draft
published: false, no published_at. Visible only in admin under Online Store → Blog posts.
Activity timeline · what you’ll see
- job_queuedidempotency_key=8b3c…
- adapter_requestPOST/blogs/{id}/articles.json
- adapter_response201894msarticle_id=87613014124
- verify_startGET https://yourstore.com/blogs/news/how-to-…
- verify_complete200412msscore 78/100 · JSON-LD in body, not <head>
- job_succeededVerifier note: head canonical missing (theme limitation)
That 78/100 score is the typical Shopify result on default themes. Follow the Liquid edits below to lift it into the 90s.
After publishing — make it shine
Move JSON-LD to <head> via theme edit
Open Online Store → Themes → Edit code → layout/theme.liquid. Inside <head>, add:{% if template contains 'article' %}{{ article.metafields.global.jsonld | escape | strip_html }}{% endif %}. Then in SEORAV settings for this connection, set jsonld_to_metafield: true in adapter_metadata so we ship JSON-LD as a metafield, not inline body.
Set per-article canonical
Same theme.liquid edit, add <link rel="canonical" href="{{ canonical_url }}"> inside the <head> block. Shopify exposes canonical_url as a Liquid variable on every page automatically.
Add structured-data partials
Better solution: install one of the structured-data themes (Dawn 11+, Sense, Trade) — they have a structured-data.liquid snippet that’s already wired into the head. SEORAV’s metafields auto-populate them.
Image-resize for performance
Shopify CDN supports query-string resizing: ?width=1200. Update your theme’s article-template.liquid to use {{ article.image | image_url: width: 1200 }} for hero — saves 60%+ bytes on mobile.
Cross-link to product pages
Shopify’s real strength is the product catalog. After publish, add 1-2 product cards inline in the article via the editor — these convert dramatically better than text-only blog posts.
Submit URL to Search Console
Shopify automatically generates a /sitemap.xml. Make sure GSC has your store verified, then submit the sitemap. New articles take 2-7 days to appear without manual submission.
Troubleshooting
401 / “Invalid API key”
Token format wrong. Custom App tokens start with shpat_and contain only base32 characters. If your token starts with anything else, you’re using the API secret (a different value) — go back and copy the access token, not the secret.
404 on POST
Wrong blog ID. Visit /admin/api/2024-10/blogs.jsonand pick the right one. Multi-blog stores often have a primary “news” blog and a side “learn” blog with different IDs.
Tags not separating
We send tags comma-separated. If they appear as a single tag like "reverse osmosis,water filtration", your store has a custom Liquid filter mangling them. Check your theme’s tag rendering in article-template.liquid.
JSON-LD validates but Google won’t pick it up
It’s in body, not head. Move it via the Liquid edit above. Test with Rich Results Test — should show 0 errors and parsed Article + FAQPage entities.
Featured image returns 422
Shopify limits inbound images to 20MB and rejects WebP on older themes. SEORAV-generated heroes are usually well under 1MB but always JPG-encoded for max compatibility — if you’re seeing rejections, paste the URL of the failing image into us and we’ll investigate.
Security & best practices
shpat_ tokens encrypted with AES-256-GCM at rest. RLS blocks the authenticated role; only the publish worker can decrypt. If a token leaks, revoke at Apps and sales channels → Develop apps → SEORAV → Uninstall; the token becomes invalid immediately.- Use a dedicated Custom App for SEORAV.
- Don’t hand the access token to multiple integrations — Shopify’s API audit log won’t distinguish callers.
- Watch Settings → Apps → SEORAV → Logs — every publish, image upload, and verify-fetch should appear there.
- Lock down the Custom App scopes to only
content+files. Shopify lets you grant more, but more scope = more blast radius if leaked.