$npx -y skills add jezweb/claude-skills --skill shopify-contentCreate and manage Shopify pages, blog posts, navigation menus, redirects, and SEO metadata via the Admin API or browser automation. Use whenever the user wants to add a page to a Shopify store, write a Shopify blog post, update the storefront navigation, manage redirects, or tune
| 1 | # Shopify Content |
| 2 | |
| 3 | Create and manage Shopify store content — pages, blog posts, navigation menus, and SEO metadata. Produces live content in the store via the Admin API or browser automation. |
| 4 | |
| 5 | ## Prerequisites |
| 6 | |
| 7 | - Admin API access token with `read_content`, `write_content` scopes (use **shopify-setup** skill) |
| 8 | - For navigation: `read_online_store_navigation`, `write_online_store_navigation` scopes |
| 9 | |
| 10 | ## Workflow |
| 11 | |
| 12 | ### Step 1: Determine Content Type |
| 13 | |
| 14 | | Content Type | API Support | Method | |
| 15 | |-------------|-------------|--------| |
| 16 | | Pages | Full | GraphQL Admin API | |
| 17 | | Blog posts | Full | GraphQL Admin API | |
| 18 | | Navigation menus | Limited | Browser automation preferred | |
| 19 | | Redirects | Full | REST Admin API | |
| 20 | | SEO metadata | Per-resource | GraphQL on the resource | |
| 21 | | Metaobjects | Full | GraphQL Admin API | |
| 22 | |
| 23 | ### Step 2a: Create Pages |
| 24 | |
| 25 | ```bash |
| 26 | curl -s https://{store}/admin/api/2025-01/graphql.json \ |
| 27 | -H "Content-Type: application/json" \ |
| 28 | -H "X-Shopify-Access-Token: {token}" \ |
| 29 | -d '{ |
| 30 | "query": "mutation pageCreate($page: PageCreateInput!) { pageCreate(page: $page) { page { id title handle } userErrors { field message } } }", |
| 31 | "variables": { |
| 32 | "page": { |
| 33 | "title": "About Us", |
| 34 | "handle": "about", |
| 35 | "body": "<h2>Our Story</h2><p>Content here...</p>", |
| 36 | "isPublished": true, |
| 37 | "seo": { |
| 38 | "title": "About Us | Store Name", |
| 39 | "description": "Learn about our story and mission." |
| 40 | } |
| 41 | } |
| 42 | } |
| 43 | }' |
| 44 | ``` |
| 45 | |
| 46 | **Page body** accepts HTML. Keep it semantic: |
| 47 | - Use `<h2>` through `<h6>` for headings (the page title is `<h1>`) |
| 48 | - Use `<p>`, `<ul>`, `<ol>` for body text |
| 49 | - Use `<a href="...">` for links |
| 50 | - Avoid inline styles — the theme handles styling |
| 51 | |
| 52 | ### Step 2b: Create Blog Posts |
| 53 | |
| 54 | Shopify blogs have a two-level structure: **Blog** (container) > **Article** (post). |
| 55 | |
| 56 | **Find or create a blog**: |
| 57 | |
| 58 | ```graphql |
| 59 | { |
| 60 | blogs(first: 10) { |
| 61 | edges { |
| 62 | node { id title handle } |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | ``` |
| 67 | |
| 68 | Most stores have a default blog called "News". Create articles in it: |
| 69 | |
| 70 | ```graphql |
| 71 | mutation { |
| 72 | articleCreate(article: { |
| 73 | blogId: "gid://shopify/Blog/123" |
| 74 | title: "New Product Launch" |
| 75 | handle: "new-product-launch" |
| 76 | contentHtml: "<p>We're excited to announce...</p>" |
| 77 | author: { name: "Store Team" } |
| 78 | tags: ["news", "products"] |
| 79 | isPublished: true |
| 80 | publishDate: "2026-02-22T00:00:00Z" |
| 81 | seo: { |
| 82 | title: "New Product Launch | Store Name" |
| 83 | description: "Announcing our latest product range." |
| 84 | } |
| 85 | image: { |
| 86 | src: "https://example.com/blog-image.jpg" |
| 87 | altText: "New product collection" |
| 88 | } |
| 89 | }) { |
| 90 | article { id title handle } |
| 91 | userErrors { field message } |
| 92 | } |
| 93 | } |
| 94 | ``` |
| 95 | |
| 96 | ### Step 2c: Update Navigation Menus |
| 97 | |
| 98 | Navigation menus have limited API support. Use browser automation: |
| 99 | |
| 100 | 1. Navigate to `https://{store}.myshopify.com/admin/menus` |
| 101 | 2. Select the menu to edit (typically "Main menu" or "Footer menu") |
| 102 | 3. Add, reorder, or remove menu items |
| 103 | 4. Save changes |
| 104 | |
| 105 | Alternatively, use the GraphQL `menuUpdate` mutation if the API version supports it: |
| 106 | |
| 107 | ```graphql |
| 108 | mutation menuUpdate($id: ID!, $items: [MenuItemInput!]!) { |
| 109 | menuUpdate(id: $id, items: $items) { |
| 110 | menu { id title } |
| 111 | userErrors { field message } |
| 112 | } |
| 113 | } |
| 114 | ``` |
| 115 | |
| 116 | ### Step 2d: Create Redirects |
| 117 | |
| 118 | URL redirects use the REST API: |
| 119 | |
| 120 | ```bash |
| 121 | curl -s https://{store}/admin/api/2025-01/redirects.json \ |
| 122 | -H "Content-Type: application/json" \ |
| 123 | -H "X-Shopify-Access-Token: {token}" \ |
| 124 | -d '{ |
| 125 | "redirect": { |
| 126 | "path": "/old-page", |
| 127 | "target": "/new-page" |
| 128 | } |
| 129 | }' |
| 130 | ``` |
| 131 | |
| 132 | ### Step 2e: Update SEO Metadata |
| 133 | |
| 134 | SEO fields are on each resource (product, page, article). Update via the resource's mutation: |
| 135 | |
| 136 | ```graphql |
| 137 | mutation { |
| 138 | pageUpdate(page: { |
| 139 | id: "gid://shopify/Page/123" |
| 140 | seo: { |
| 141 | title: "Updated SEO Title" |
| 142 | description: "Updated meta description under 160 chars." |
| 143 | } |
| 144 | }) { |
| 145 | page { id title } |
| 146 | userErrors { field message } |
| 147 | } |
| 148 | } |
| 149 | ``` |
| 150 | |
| 151 | ### Step 3: Verify |
| 152 | |
| 153 | Query back the content to confirm: |
| 154 | |
| 155 | ```graphql |
| 156 | { |
| 157 | pages(first: 10, reverse: true) { |
| 158 | edges { |
| 159 | node { id title handle isPublished createdAt } |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | ``` |
| 164 | |
| 165 | Provide the admin URL and the live URL for the user to review: |
| 166 | - Admin: `https://{store}.myshopify.com/admin/pages` |
| 167 | - Live: `https://{store}.myshopify.com/pages/{handle}` |
| 168 | |
| 169 | --- |
| 170 | |
| 171 | ## Critical Patterns |
| 172 | |
| 173 | ### Page vs Metaobject |
| 174 | |
| 175 | For simple content (About, Contact, FAQ), use **pages**. For structured, repeatable content (team members, testimonials, locations), use **metaobjects** — they have typed fields and can be queried programmatically. |
| 176 | |
| 177 | ### Blog SEO |
| 178 | |
| 179 | Every blog post should have: |
| 180 | - **SEO title**: und |