Install
$ agentstack add skill-boshify-ai-seo-agent-skills-shopify ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Shopify Publishing
What This Skill Does
Publishes HTML content to a Shopify store as blog articles or static pages, with SEO metafields and optional featured images. It handles authentication, blog discovery, article/page creation and updates, image uploads, and rate limit management.
Context the Agent Needs
- Shopify REST Admin API uses version-dated endpoints (use
2024-01as the stable version). - Authentication is a single header:
X-Shopify-Access-Token: TOKEN-- no OAuth flow needed for Custom Apps. - Blog articles live under a specific blog ID; a store can have multiple blogs. Pages are top-level and have no parent blog.
- SEO title and meta description are set via metafields with namespace
globaland keystitle_tag/description_tag, not via dedicated fields. - Rate limit is 2 requests per second using a leaky bucket algorithm. The
X-Shopify-Shop-Api-Call-Limitheader (e.g.,2/40) shows current usage. Slow down when the numerator approaches the denominator. - Tags in Shopify are a single comma-separated string, not a JSON array.
Workflow Steps
STEP 1: Validate Credentials
Confirm the access token and store URL are valid before attempting any writes.
Input: shopify-store-url, shopify-access-token Process:
- Run the auth test request:
``bash curl -s "https://YOUR-STORE.myshopify.com/admin/api/2024-01/shop.json" \ -H "X-Shopify-Access-Token: YOUR_TOKEN" | jq '.shop.name' ``
- Confirm the response returns a shop name (not an error object).
Output: Confirmed store name and valid credentials. Decision gate:
- If shop name is returned -> proceed to Step 2
- If 401/403 error -> stop and report: token is invalid or missing
write_contentscope - If connection error -> stop and report: store URL is incorrect
STEP 2: Determine Content Type
Decide whether to create a blog article or a page based on the content and user intent.
Input: User request, content metadata Process:
- If user specifies "page" or content is a landing page / about page / policy -> target is a Page.
- If user specifies "blog post" or "article", or content has a date/author -> target is a Blog Article.
- If ambiguous, default to Blog Article (most common SEO publishing target).
Output: Decision: article or page. Decision gate:
- If
article-> proceed to Step 3 - If
page-> skip to Step 4
STEP 3: Discover Blog ID
Retrieve the target blog ID. Required for article creation.
Input: shopify-store-url, shopify-access-token, optional blog-id Process:
- If
blog-idis already provided, use it directly. - Otherwise, list all blogs:
``bash curl -s "https://YOUR-STORE.myshopify.com/admin/api/2024-01/blogs.json" \ -H "X-Shopify-Access-Token: YOUR_TOKEN" | jq '.blogs[] | {id, title, handle}' ``
- If the user named a specific blog, match by title or handle.
- If only one blog exists, use it. If multiple exist and none specified, ask the user.
Output: Numeric blog-id to target. Decision gate:
- If blog ID resolved -> proceed to Step 4
- If no blogs exist -> stop and report: store has no blogs configured
- If multiple blogs and no match -> ask user which blog to target
STEP 4: Prepare Payload
Build the JSON payload for the article or page.
Input: html-content, seo-metadata, featured-image-url, content type decision Process:
- Build the base object with
title,body_html,published: true. - If SEO metadata is provided, add metafields array:
``json "metafields": [ { "namespace": "global", "key": "title_tag", "value": "SEO Title Here", "type": "single_line_text_field" }, { "namespace": "global", "key": "description_tag", "value": "Meta description here", "type": "single_line_text_field" } ] ``
- For articles: add
author,tags(comma-separated string), andsummary_htmlif excerpt is available. - For articles with a featured image: add
"image": {"src": "https://cdn.com/image.jpg"}to the article object. - For scheduled publishing: set
published_atto an ISO 8601 datetime (e.g.,"2026-04-01T09:00:00-05:00"). - For custom URL slugs: set
handleto the desired slug.
Output: Complete JSON payload ready for the API call. Decision gate:
- If payload is valid JSON -> proceed to Step 5
- If required fields are missing (title or body_html) -> stop and ask user for missing content
STEP 5: Publish to Shopify
Send the create or update request to the Shopify Admin API.
Input: JSON payload, blog-id (for articles), content type Process:
- For a new article:
``bash curl -s -X POST \ "https://YOUR-STORE.myshopify.com/admin/api/2024-01/blogs/BLOG_ID/articles.json" \ -H "X-Shopify-Access-Token: YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"article": { ...payload... }}' ``
- For a new page:
``bash curl -s -X POST \ "https://YOUR-STORE.myshopify.com/admin/api/2024-01/pages.json" \ -H "X-Shopify-Access-Token: YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"page": { ...payload... }}' ``
- For an update to an existing article:
``bash curl -s -X PUT \ "https://YOUR-STORE.myshopify.com/admin/api/2024-01/blogs/BLOG_ID/articles/ARTICLE_ID.json" \ -H "X-Shopify-Access-Token: YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"article": { ...payload... }}' ``
- For an update to an existing page:
``bash curl -s -X PUT \ "https://YOUR-STORE.myshopify.com/admin/api/2024-01/pages/PAGE_ID.json" \ -H "X-Shopify-Access-Token: YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"page": { ...payload... }}' ``
- Check the
X-Shopify-Shop-Api-Call-Limitheader. If approaching the limit, add a 500ms delay before the next request. - Parse the response to extract the created/updated resource ID and handle.
Output: Shopify API response with resource ID, handle, and confirmation of publish status. Decision gate:
- If 201 Created or 200 OK -> proceed to Step 6
- If 422 Unprocessable Entity -> inspect
errorsobject, fix payload, and retry once - If 429 Too Many Requests -> wait 1 second and retry
- If 401/403 -> stop and report credentials or scope issue
STEP 6: Upload Image to Shopify Files (Optional)
Upload an image to Shopify's file storage via GraphQL when needed for non-article images.
Input: Image URL to upload Process:
- Send the GraphQL mutation:
``bash curl -s -X POST \ "https://YOUR-STORE.myshopify.com/admin/api/2024-01/graphql.json" \ -H "X-Shopify-Access-Token: YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "query": "mutation fileCreate($files: [FileCreateInput!]!) { fileCreate(files: $files) { files { alt url } userErrors { field message } } }", "variables": { "files": [{ "alt": "Image description", "contentType": "IMAGE", "originalSource": "https://cdn.com/image.jpg" }] } }' ``
- Check
userErrorsfor failures. - Extract the Shopify-hosted URL from the response.
Output: Shopify CDN URL for the uploaded image. Decision gate:
- If
userErrorsis empty -> use the returned URL - If
userErrorspresent -> report the error; fall back to inline `` with the original URL
STEP 7: Verify and Report
Confirm the publish was successful and provide the public URL.
Input: API response from Step 5 Process:
- Extract the resource
idandhandlefrom the response. - Construct the public URL:
- Articles:
https://your-store.myshopify.com/blogs/BLOG_HANDLE/ARTICLE_HANDLE - Pages:
https://your-store.myshopify.com/pages/PAGE_HANDLE
- Report the result in the output format below.
Output: Structured confirmation with ID, handle, URL, and publish status. Decision gate:
- If URL is accessible -> done
- If custom domain is configured -> note that the
.myshopify.comURL redirects to the custom domain
Output Format
SHOPIFY PUBLISH RESULT
======================
Status: [Created | Updated]
Type: [Article | Page]
ID: [numeric ID]
Handle: [url-slug]
Blog: [blog title] (articles only)
URL: https://your-store.myshopify.com/[blogs/handle/article-handle | pages/page-handle]
SEO Title: [title_tag value or "not set"]
SEO Desc: [description_tag value or "not set"]
Image: [featured image URL or "none"]
Published: [true | false | scheduled for YYYY-MM-DD]
Edge Cases & Judgment Calls
- Missing HTML content: If only a title is provided with no body, create the resource with an empty
body_htmland warn the user that the published page/article has no content. Do not refuse to publish.
- Blog does not exist: If the user references a blog by name that does not exist, list available blogs and ask for clarification. Do not create a new blog -- that requires separate store configuration.
- Rate limit hit during bulk publishing: When publishing multiple articles in sequence, check
X-Shopify-Shop-Api-Call-Limitafter each request. If the numerator exceeds 75% of the denominator (e.g., 30/40), insert a 1-second delay before the next call. If a 429 response is received, wait 2 seconds and retry.
- Duplicate article title: Shopify allows duplicate titles (it auto-generates unique handles). Warn the user if an article with the same title already exists in the target blog, but proceed with creation unless explicitly told to update instead.
- Image URL is not publicly accessible: If the featured image
srcreturns a non-200 status, Shopify will silently ignore it. Warn the user that the image may not appear. Suggest uploading to Shopify Files via GraphQL as a fallback.
- Metafields on update: When updating an existing article or page, metafields must be sent with their existing
idto update them, or omitted entirely to leave them unchanged. Sending metafields without anidon an update creates duplicates. If updating, first GET the resource to retrieve existing metafield IDs.
- HTML contains inline styles or scripts: Shopify strips `
tags and certain inline styles frombody_html`. Do not attempt to include JavaScript. Warn the user if the HTML contains scripts that will be stripped.
- Large body_html (over 512KB): Shopify has undocumented size limits on
body_html. If the content is very large, warn the user and suggest breaking it into multiple pages or trimming.
What This Skill Does NOT Do
- Create or configure Shopify Custom Apps -- the user must create the app and provide the access token.
- Manage Shopify products, collections, or inventory -- this skill is limited to content publishing (articles and pages).
- Create new blogs -- only publishes to existing blogs. Blog creation is a store administration task.
- Handle Shopify theme/template editing -- no Liquid template modifications.
- Manage redirects or URL routing -- use Shopify admin for URL redirects.
- Generate content -- this skill publishes content; use the
ai-seo-engineskill to generate it. - Manage Shopify webhooks or app subscriptions -- out of scope.
Recipes
List Existing Articles
# Count articles in a blog
curl -s "https://YOUR-STORE.myshopify.com/admin/api/2024-01/blogs/BLOG_ID/articles/count.json" \
-H "X-Shopify-Access-Token: YOUR_TOKEN" | jq '.count'
# List articles (paginated, 50 per page)
curl -s "https://YOUR-STORE.myshopify.com/admin/api/2024-01/blogs/BLOG_ID/articles.json?limit=50" \
-H "X-Shopify-Access-Token: YOUR_TOKEN" | jq '.articles[] | {id, title, handle, published_at}'
Delete an Article
curl -s -X DELETE \
"https://YOUR-STORE.myshopify.com/admin/api/2024-01/blogs/BLOG_ID/articles/ARTICLE_ID.json" \
-H "X-Shopify-Access-Token: YOUR_TOKEN"
Schedule a Future Publish
Set published_at to a future ISO 8601 datetime and published to true:
{
"article": {
"title": "Scheduled Post",
"body_html": "Content here",
"published": true,
"published_at": "2026-04-01T09:00:00-05:00"
}
}
Set a Custom URL Slug
Use the handle field to control the URL path:
{
"article": {
"title": "My Article Title",
"handle": "custom-url-slug",
"body_html": "Content"
}
}
Examples
Example 1: Happy Path -- Publish a Blog Article with SEO Metadata
Scenario: User has HTML content from AI SEO Engine and wants to publish it to their Shopify blog.
Inputs provided:
- Store URL:
https://acme-store.myshopify.com - Access token:
shpat_xxxxx - HTML content:
Best Running Shoes 2026Our top picks... - SEO title:
Best Running Shoes 2026 | Acme Store - SEO description:
Discover the top running shoes for 2026 with expert reviews and comparisons. - Featured image:
https://cdn.example.com/running-shoes.jpg
Agent execution:
- Validates credentials --
shop.namereturns "Acme Store". - Content type: article (blog post with SEO intent).
- Lists blogs -- finds one blog:
{id: 82471234, title: "News", handle: "news"}. - Builds payload with title, body_html, metafields for SEO, featured image, and tags.
- POSTs to
/admin/api/2024-01/blogs/82471234/articles.json. - Gets 201 response with
{id: 991234567, handle: "best-running-shoes-2026"}.
Output:
SHOPIFY PUBLISH RESULT
======================
Status: Created
Type: Article
ID: 991234567
Handle: best-running-shoes-2026
Blog: News
URL: https://acme-store.myshopify.com/blogs/news/best-running-shoes-2026
SEO Title: Best Running Shoes 2026 | Acme Store
SEO Desc: Discover the top running shoes for 2026 with expert reviews and comparisons.
Image: https://cdn.example.com/running-shoes.jpg
Published: true
Example 2: Edge Case -- Multiple Blogs, No Blog Specified
Scenario: User says "publish this article to Shopify" but the store has 3 blogs.
Inputs provided:
- Store URL:
https://acme-store.myshopify.com - Access token:
shpat_xxxxx - HTML content:
Article body - No blog ID or blog name specified
Agent execution:
- Validates credentials -- success.
- Content type: article.
- Lists blogs -- finds three:
`` {id: 82471234, title: "News", handle: "news"} {id: 82471235, title: "Guides", handle: "guides"} {id: 82471236, title: "Updates", handle: "updates"} ``
- Multiple blogs found and none specified -- stops and asks the user:
> "Your store has 3 blogs: News, Guides, and Updates. Which blog should I publish this article to?"
- User responds "Guides".
- Proceeds with blog ID
82471235and completes the publish.
Judgment call: The agent does not guess or default to the first blog when multiple exist. Picking the wrong blog means the article shows up in the wrong section of the store. Always ask.
Example 3: Edge Case -- Publishing a Static Page
Scenario: User wants to publish an "About Us" page to Shopify, not a blog article.
Inputs provided:
- Store URL:
https://acme-store.myshopify.com - Access token:
shpat_xxxxx - HTML content:
About AcmeFounded in 2020... - Title: "About Us"
Agent execution:
- Validates credentials -- success.
- Content type: page (user said "page" and content is an about page).
- Skips blog discovery (pages have no parent blog).
- Builds payload with title and body_html.
- POSTs to
/admin/api/2024-01/pages.json. - Gets 201 response with
{id: 112345, handle: "about-us"}.
Output:
SHOPIFY PUBLISH RESULT
======================
Status: Created
Type: Page
ID: 112345
Handle: about-us
Blog: n/a
URL: https://acme-store.myshopify.com/pages/about-us
SEO Title: not set
SEO Desc: not set
Image: none
Published: true
Judgment call: No SEO metafields were provided, so the
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: boshify
- Source: boshify/ai-seo-agent-skills
- License: MIT
- Homepage: https://www.skool.com/ai-seo/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.