Shopify logo

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.

Read this before connecting
Shopify is the most opinionated mainstream CMS we support. Your theme almost certainly does not have <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

FeatureSupportedNotes
Publish + update + unpublish YesFull CRUD via /blogs/{id}/articles.json.
Native scheduling Yespublished_at + published: false until time. Shopify cron handles flip.
Draft mode Yespublished: false. Visible only in admin.
Featured image Yesimage.src + alt — Shopify ingests + re-hosts on its CDN.
Tags YesComma-separated string (Shopify's only way to pass tags via API).
Author PartialPlain text field, not a reference. We send the author name; no author profile linkage.
Meta title + description Yesglobal.title_tag / global.description_tag metafields — read by every Shopify theme.
Canonical URL NoShopify does not expose a per-post canonical via API. Set theme-wide canonical via Liquid.
JSON-LD in <head> NoInline in body only. Liquid edit required for head placement (instructions below).
OpenGraph fields PartialShopify auto-derives OG from title + summary + featured image. Custom OG image not exposed via API.
Multi-blog (multiple blogs in one store) YesConnect each blog as its own SEORAV integration with the matching blog_id.
Articles + FAQ pages YesPublishes 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.
One connection per content type
Shopify connections are scoped to a single content type. To publish both long-form articles AND FAQ / answer-engine pages from the same store, add two Shopify connections — one for articles, one for answer pages. The same Admin API token works for both; give each connection a distinct blog_idso articles and answers don’t share the same blog index.

Setup

  1. 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 prefix shpat_.
  2. 2

    Configure Admin API scopes

    Click Configure Admin API scopes and enable:

    • read_content, write_content — blog posts + pages
    • read_files, write_files — featured image upload

    Save, then click Install app at the top right.

  3. 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. 4

    Find your Blog ID

    Visit https://yourstore.myshopify.com/admin/api/2024-10/blogs.json in your browser (you’ll need to be logged in). Find your blog and note the numeric id. Most stores have one blog with id like 87613014123.

  5. 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

http
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

PropertyTypeDescription
title, handle, body_htmlstringDirect mapping. handle is Shopify's URL slug.
tagsstringComma-separated. Shopify's API doesn't accept arrays for tags.
authorstringPlain text. No reference to a customer or staff profile.
image.src + image.altstringHero URL + alt. Shopify downloads + re-hosts on its CDN automatically.
publishedbooleantrue / false. Combined with published_at for scheduling.
metafields[global.title_tag]metafieldSets the <title> tag on the rendered page (overrides the theme default).
metafields[global.description_tag]metafieldSets <meta name="description">.
jsonld_blocks (inline)HTMLJoined 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

Activity timeline · what shows up in your dashboard
  • 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

Storage
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.

Useful links