npx skills add ...
npx skills add jezweb/claude-skills --skill shopify-products
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.
npx skills add jezweb/claude-skills --skill shopify-products
Create, update, and bulk-import Shopify products. Produces live products in the store via the GraphQL Admin API or CSV import.
shopify.config.json or .dev.varsDetermine what the user wants to create or update:
Accept data from:
| 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 |
Variables:
Curl example:
Batching multiple products: Create products sequentially with a short delay between each to respect rate limits (1,000 cost points/second).
Variables include id (required) plus any fields to update.
Add variants to an existing product:
For 20+ products, generate a CSV and import through Shopify admin.
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 |
SEO Description | Meta description | Premium cotton tee in 5 colours |
The first row has the product title and details. Subsequent rows for the same product have only the Handle and variant-specific columns:
Published = TRUE makes the product immediately visibleassets/product-csv-template.csv if availablehttps://{store}.myshopify.com/admin/products/importUse browser automation to assist with the upload if needed.
Images require a two-step process -- staged upload then attach.
Input per file:
Then upload to the staged URL, and attach with productCreateMedia:
Shortcut: If images are already hosted at a public URL, pass src directly in the product creation:
To find collection IDs:
Input:
To find location IDs:
Query back the created products to confirm:
Provide the admin URL for the user to review: https://{store}.myshopify.com/admin/products
New products default to DRAFT. To make them visible:
Always confirm with the user before setting status to ACTIVE.
Shopify allows max 100 variants per product and 3 options (e.g. Size, Colour, Material). If you need more, split into separate products.
Prices are strings, not numbers. Always quote them: "price": "29.95" not "price": 29.95.
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 insteadFor 50+ products via API, use Shopify's bulk operation:
This accepts a JSONL file with one product per line, processed asynchronously.
assets/product-csv-template.csv -- Blank CSV template with Shopify import headers{
"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 -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": { ... }}'mutation productUpdate($input: ProductInput!) {
productUpdate(input: $input) {
product { id title }
userErrors { field message }
}
}mutation productDelete($input: ProductDeleteInput!) {
productDelete(input: $input) {
deletedProductId
userErrors { field message }
}
}mutation productVariantsBulkCreate($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
productVariantsBulkCreate(productId: $productId, variants: $variants) {
productVariants { id title price }
userErrors { field message }
}
}mutation productVariantsBulkUpdate($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
productVariantsBulkUpdate(productId: $productId, variants: $variants) {
productVariants { id title price }
userErrors { field message }
}
}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,mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets {
url
resourceUrl
parameters { name value }
}
userErrors { field message }
}
}{
"filename": "product-image.jpg",
"mimeType": "image/jpeg",
"httpMethod": "POST",
"resource": "IMAGE"
}mutation productCreateMedia($productId: ID!, $media: [CreateMediaInput!]!) {
productCreateMedia(productId: $productId, media: $media) {
media { alt status }
mediaUserErrors { field message }
}
}{
"images": [
{ "src": "https://example.com/image.jpg", "alt": "Product front view" }
]
}mutation collectionAddProducts($id: ID!, $productIds: [ID!]!) {
collectionAddProducts(id: $id, productIds: $productIds) {
collection { title productsCount }
userErrors { field message }
}
}{
collections(first: 50) {
edges {
node { id title handle productsCount }
}
}
}mutation inventorySetQuantities($input: InventorySetQuantitiesInput!) {
inventorySetQuantities(input: $input) {
inventoryAdjustmentGroup { reason }
userErrors { field message }
}
}{
"reason": "correction",
"name": "available",
"quantities": [{
"inventoryItemId": "gid://shopify/InventoryItem/123",
"locationId": "gid://shopify/Location/456",
"quantity": 50
}]
}{
locations(first: 10) {
edges {
node { id name isActive }
}
}
}{
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 }
}
}{ "status": "ACTIVE" }mutation {
bulkOperationRunMutation(
mutation: "mutation ($input: ProductInput!) { productCreate(input: $input) { product { id } userErrors { message } } }"
stagedUploadPath: "tmp/bulk-products.jsonl"
) {
bulkOperation { id status }
userErrors { message }
}
}