shopify-products

$npx mdskill add jezweb/claude-skills/shopify-products

Create and manage Shopify products via API or CSV import.

  • Adds products to Shopify from conversation, CSV, or URL.
  • Requires Admin API token and store URL from shopify-setup.
  • Chooses GraphQL for small batches or CSV import for bulk.
  • Executes mutations or imports and verifies product creation.

SKILL.md

.github/skills/shopify-productsView on GitHub ↗
---
name: shopify-products
description: "Create and manage Shopify products via the Admin GraphQL API or CSV import. Workflow: gather data, choose method, execute, verify. Use whenever the user wants to add products to Shopify, bulk-import a catalog from CSV/spreadsheet/URL, update variants or prices, manage inventory quantities, upload product images, or assign products to collections."
compatibility: claude-code-only
---

# Shopify Products

Create, update, and bulk-import Shopify products. Produces live products in the store via the GraphQL Admin API or CSV import.

## Prerequisites

- Admin API access token (use the **shopify-setup** skill if not configured)
- Store URL and API version from `shopify.config.json` or `.dev.vars`

## Workflow

### Step 1: Gather Product Data

Determine what the user wants to create or update:

- **Product basics**: title, description (HTML), product type, vendor, tags
- **Variants**: options (size, colour, material), prices, SKUs, inventory quantities
- **Images**: URLs to upload, or local files
- **SEO**: page title, meta description, URL handle
- **Organisation**: collections, product type, tags

Accept data from:
- Direct conversation (user describes products)
- Spreadsheet/CSV file (user provides a file)
- Website scraping (user provides a URL to extract from)

### Step 2: Choose Method

| Scenario | Method |
|----------|--------|
| 1-5 products | GraphQL mutations |
| 6-20 products | GraphQL with batching |
| 20+ products | CSV import via admin |
| Updates to existing | GraphQL mutations |
| Inventory adjustments | `inventorySetQuantities` mutation |

---

### Step 3a: Create via GraphQL (Recommended)

#### productCreate

```graphql
mutation productCreate($product: ProductCreateInput!) {
  productCreate(product: $product) {
    product {
      id
      title
      handle
      status
      variants(first: 100) {
        edges {
          node { id title price sku inventoryQuantity }
        }
      }
    }
    userErrors { field message }
  }
}
```

Variables:

```json
{
  "product": {
    "title": "Example T-Shirt",
    "descriptionHtml": "<p>Premium cotton tee</p>",
    "vendor": "My Brand",
    "productType": "T-Shirts",
    "tags": ["summer", "cotton"],
    "status": "DRAFT",
    "options": ["Size", "Colour"],
    "variants": [
      {
        "optionValues": [
          {"optionName": "Size", "name": "S"},
          {"optionName": "Colour", "name": "Black"}
        ],
        "price": "29.95",
        "sku": "TSHIRT-S-BLK",
        "inventoryPolicy": "DENY",
        "inventoryItem": { "tracked": true }
      },
      {
        "optionValues": [
          {"optionName": "Size", "name": "M"},
          {"optionName": "Colour", "name": "Black"}
        ],
        "price": "29.95",
        "sku": "TSHIRT-M-BLK"
      },
      {
        "optionValues": [
          {"optionName": "Size", "name": "L"},
          {"optionName": "Colour", "name": "Black"}
        ],
        "price": "29.95",
        "sku": "TSHIRT-L-BLK"
      }
    ],
    "seo": {
      "title": "Example T-Shirt | My Brand",
      "description": "Premium cotton tee in multiple sizes"
    }
  }
}
```

Curl example:

```bash
curl -s https://{store}/admin/api/2025-01/graphql.json \
  -H "Content-Type: application/json" \
  -H "X-Shopify-Access-Token: {token}" \
  -d '{"query": "mutation productCreate($product: ProductCreateInput!) { productCreate(product: $product) { product { id title } userErrors { field message } } }", "variables": { ... }}'
```

**Batching multiple products**: Create products sequentially with a short delay between each to respect rate limits (1,000 cost points/second).

#### productUpdate

```graphql
mutation productUpdate($input: ProductInput!) {
  productUpdate(input: $input) {
    product { id title }
    userErrors { field message }
  }
}
```

Variables include `id` (required) plus any fields to update.

#### productDelete

```graphql
mutation productDelete($input: ProductDeleteInput!) {
  productDelete(input: $input) {
    deletedProductId
    userErrors { field message }
  }
}
```

#### productVariantsBulkCreate

Add variants to an existing product:

```graphql
mutation productVariantsBulkCreate($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
  productVariantsBulkCreate(productId: $productId, variants: $variants) {
    productVariants { id title price }
    userErrors { field message }
  }
}
```

#### productVariantsBulkUpdate

```graphql
mutation productVariantsBulkUpdate($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
  productVariantsBulkUpdate(productId: $productId, variants: $variants) {
    productVariants { id title price }
    userErrors { field message }
  }
}
```

---

### Step 3b: Bulk Import via CSV

For 20+ products, generate a CSV and import through Shopify admin.

#### CSV Column Reference

**Required columns:**

| Column | Description | Example |
|--------|-------------|---------|
| `Handle` | URL slug (unique per product) | `classic-tshirt` |
| `Title` | Product name (first row per product) | `Classic T-Shirt` |
| `Body (HTML)` | Description in HTML | `<p>Premium cotton</p>` |
| `Vendor` | Brand or manufacturer | `My Brand` |
| `Product Category` | Shopify standard taxonomy | `Apparel & Accessories > Clothing > Shirts & Tops` |
| `Type` | Custom product type | `T-Shirts` |
| `Tags` | Comma-separated tags | `summer, cotton, casual` |
| `Published` | Whether product is visible | `TRUE` or `FALSE` |

**Variant columns:**

| Column | Description | Example |
|--------|-------------|---------|
| `Option1 Name` | First option name | `Size` |
| `Option1 Value` | First option value | `Medium` |
| `Option2 Name` | Second option name | `Colour` |
| `Option2 Value` | Second option value | `Black` |
| `Option3 Name` | Third option name | `Material` |
| `Option3 Value` | Third option value | `Cotton` |
| `Variant SKU` | Stock keeping unit | `TSHIRT-M-BLK` |
| `Variant Grams` | Weight in grams | `200` |
| `Variant Inventory Qty` | Stock quantity | `50` |
| `Variant Price` | Variant price | `29.95` |
| `Variant Compare At Price` | Original price (for sales) | `39.95` |
| `Variant Requires Shipping` | Physical product | `TRUE` |
| `Variant Taxable` | Subject to tax | `TRUE` |

**Image columns:**

| Column | Description | Example |
|--------|-------------|---------|
| `Image Src` | Image URL | `https://example.com/img.jpg` |
| `Image Position` | Display order (1-based) | `1` |
| `Image Alt Text` | Alt text for accessibility | `Classic T-Shirt front view` |

**SEO columns:**

| Column | Description | Example |
|--------|-------------|---------|
| `SEO Title` | Page title tag | `Classic T-Shirt | My Brand` |
| `SEO Description` | Meta description | `Premium cotton tee in 5 colours` |

#### Multi-Variant Row Format

The first row has the product title and details. Subsequent rows for the same product have only the `Handle` and variant-specific columns:

```csv
Handle,Title,Body (HTML),Vendor,Type,Tags,Published,Option1 Name,Option1 Value,Variant SKU,Variant Price,Variant Inventory Qty,Image Src
classic-tshirt,Classic T-Shirt,<p>Premium cotton</p>,My Brand,T-Shirts,"summer,cotton",TRUE,Size,Small,TSH-S,29.95,50,https://example.com/tshirt.jpg
classic-tshirt,,,,,,,,Medium,TSH-M,29.95,75,
classic-tshirt,,,,,,,,Large,TSH-L,29.95,60,
```

#### CSV Rules

- UTF-8 encoding required
- Maximum 50MB file size
- Handle must be unique per product -- duplicate handles update existing products
- Leave variant columns blank on variant rows for fields that don't change
- Images can be on any row -- they're associated by Handle
- `Published` = `TRUE` makes the product immediately visible

#### Import Steps

1. Generate CSV using the column format above
2. Use the template from `assets/product-csv-template.csv` if available
3. Navigate to `https://{store}.myshopify.com/admin/products/import`
4. Upload the CSV file
5. Review the preview and confirm import

Use browser automation to assist with the upload if needed.

---

### Step 4: Upload Product Images

Images require a two-step process -- staged upload then attach.

#### stagedUploadsCreate

```graphql
mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
  stagedUploadsCreate(input: $input) {
    stagedTargets {
      url
      resourceUrl
      parameters { name value }
    }
    userErrors { field message }
  }
}
```

Input per file:

```json
{
  "filename": "product-image.jpg",
  "mimeType": "image/jpeg",
  "httpMethod": "POST",
  "resource": "IMAGE"
}
```

Then upload to the staged URL, and attach with `productCreateMedia`:

#### productCreateMedia

```graphql
mutation productCreateMedia($productId: ID!, $media: [CreateMediaInput!]!) {
  productCreateMedia(productId: $productId, media: $media) {
    media { alt status }
    mediaUserErrors { field message }
  }
}
```

**Shortcut**: If images are already hosted at a public URL, pass `src` directly in the product creation:

```json
{
  "images": [
    { "src": "https://example.com/image.jpg", "alt": "Product front view" }
  ]
}
```

---

### Step 5: Assign to Collections

#### collectionAddProducts

```graphql
mutation collectionAddProducts($id: ID!, $productIds: [ID!]!) {
  collectionAddProducts(id: $id, productIds: $productIds) {
    collection { title productsCount }
    userErrors { field message }
  }
}
```

To find collection IDs:

```graphql
{
  collections(first: 50) {
    edges {
      node { id title handle productsCount }
    }
  }
}
```

---

### Step 6: Set Inventory

#### inventorySetQuantities

```graphql
mutation inventorySetQuantities($input: InventorySetQuantitiesInput!) {
  inventorySetQuantities(input: $input) {
    inventoryAdjustmentGroup { reason }
    userErrors { field message }
  }
}
```

Input:

```json
{
  "reason": "correction",
  "name": "available",
  "quantities": [{
    "inventoryItemId": "gid://shopify/InventoryItem/123",
    "locationId": "gid://shopify/Location/456",
    "quantity": 50
  }]
}
```

To find location IDs:

```graphql
{
  locations(first: 10) {
    edges {
      node { id name isActive }
    }
  }
}
```

---

### Step 7: Verify

Query back the created products to confirm:

```graphql
{
  products(first: 50) {
    edges {
      node {
        id title handle status productType vendor
        variants(first: 10) {
          edges { node { id title price sku inventoryQuantity } }
        }
        images(first: 3) { edges { node { url altText } } }
      }
    }
    pageInfo { hasNextPage endCursor }
  }
}
```

Provide the admin URL for the user to review: `https://{store}.myshopify.com/admin/products`

---

## Critical Patterns

### Product Status

New products default to `DRAFT`. To make them visible:

```json
{ "status": "ACTIVE" }
```

Always confirm with the user before setting status to `ACTIVE`.

### Variant Limits

Shopify allows max **100 variants** per product and **3 options** (e.g. Size, Colour, Material). If you need more, split into separate products.

### Price Formatting

Prices are strings, not numbers. Always quote them: `"price": "29.95"` not `"price": 29.95`.

### HTML Descriptions

Product descriptions accept HTML. Keep it simple -- Shopify's editor handles basic tags:
- `<p>`, `<strong>`, `<em>`, `<ul>`, `<ol>`, `<li>`, `<h2>`-`<h6>`
- `<a href="...">` for links
- `<img>` is stripped -- use product images instead

### Bulk Operations for Large Imports

For 50+ products via API, use Shopify's bulk operation:

```graphql
mutation {
  bulkOperationRunMutation(
    mutation: "mutation ($input: ProductInput!) { productCreate(input: $input) { product { id } userErrors { message } } }"
    stagedUploadPath: "tmp/bulk-products.jsonl"
  ) {
    bulkOperation { id status }
    userErrors { message }
  }
}
```

This accepts a JSONL file with one product per line, processed asynchronously.

---

## Asset Files

- `assets/product-csv-template.csv` -- Blank CSV template with Shopify import headers

More from jezweb/claude-skills

SkillDescription
ai-image-generatorGenerate AI images using Gemini or GPT APIs directly. Covers model selection (Gemini for scenes; GPT Image 2 for text rendering, batch variations, multi-reference compositing; GPT Image 1.5 for transparent icons), the 5-part prompting framework, API calling patterns, multi-turn editing, and quality assurance. Produces photorealistic scenes, icons, illustrations, OG images, posters, infographics, and product shots. Use when building websites that need images, creating marketing assets, or generating visual content. Triggers: 'generate image', 'ai image', 'create hero image', 'make an icon', 'generate illustration', 'create og image', 'poster', 'infographic', 'image variations', 'gpt-image-2', 'ai art', 'image generation'.
app-docsGenerate complete user documentation for a web app with screenshots. Browses the app via browser automation, screenshots every screen, and produces a structured user guide with step-by-step instructions, annotated screenshots, workflow diagrams, and reference tables. Supports quick (key screens), standard (all pages), thorough (every state and flow), and exhaustive (publishable documentation suite). Triggers: 'document the app', 'user guide', 'app documentation', 'screenshot docs', 'generate user docs', 'help docs', 'how-to guide', 'write the docs'.
aussie-business-englishAustralian business English for professional writing — warm, direct, EN-AU spelling (colour, organise, centre), no filler words. Use whenever the user is writing for an Australian audience: emails, chat messages, proposals, client communications, blog posts, web copy, or any business writing. Apply to drafting, editing, and tone-checking professional text.
award-applicationWrite compelling award submissions, grant applications, and competition entries. Maps achievements to selection criteria using evidence-based narratives. Use whenever the user is applying for a business award (Telstra, chamber of commerce, industry awards), a grant, a competition, or any submission demonstrating merit against defined criteria.
cloudflare-apiHit the Cloudflare REST API directly for operations that wrangler and MCP can't handle well. Bulk DNS, custom hostnames, email routing, cache purge, WAF rules, redirect rules, zone settings, Worker routes, D1 cross-database queries, R2 bulk operations, KV bulk read/write, Vectorize queries, Queues, and fleet-wide resource audits. Produces curl commands or scripts. Triggers: 'cloudflare api', 'bulk dns', 'custom hostname', 'email routing', 'cache purge', 'waf rule', 'd1 query', 'r2 bucket', 'kv bulk', 'vectorize query', 'audit resources', 'fleet operation'.
cloudflare-worker-builderScaffold and deploy Cloudflare Workers with Hono routing, Vite plugin, and Static Assets. Describe project, scaffold structure, configure bindings, deploy. Use whenever the user wants to create a Worker project, set up Hono on Cloudflare, configure D1 / R2 / KV / Queues bindings, or troubleshoot Worker export syntax, API route conflicts, HMR issues, or deployment failures.
codex-reviewRun an independent code review using the OpenAI Codex CLI in headless mode. Gets a second opinion from a different model family (the current Codex models) on recent changes, a PR, a commit, or the whole app — covering bugs, regressions, security, data consistency, UX/state bugs, performance risks, and testing gaps. Saves a severity-prioritised report to .jez/reviews/. Triggers: 'codex review', 'review with codex', 'independent code review', 'what does codex think', 'get codex to review'.
color-paletteGenerate complete, accessible colour palettes from a single brand hex. Produces 11-shade scale (50-950), semantic tokens, dark mode variants, Tailwind v4 CSS output, WCAG contrast checks. Use whenever the user supplies a brand hex and asks for a palette, mentions setting up a design system, wants Tailwind theme colours from a brand colour, or asks to check colour accessibility / contrast.
d1-drizzle-schemaGenerate Drizzle ORM schemas for Cloudflare D1 databases with correct D1-specific patterns. Produces schema files, migration commands, type exports, and DATABASE_SCHEMA.md documentation. Handles D1 quirks: foreign keys always enforced, no native BOOLEAN/DATETIME types, 100 bound parameter limit, JSON stored as TEXT. Use when creating a new database, adding tables, or scaffolding a D1 data layer.
d1-migrationCloudflare D1 migration workflow: generate with Drizzle, inspect SQL for gotchas, apply to local and remote, fix stuck migrations, handle partial failures. Use when running migrations, fixing migration errors, or setting up D1 schemas.