Shopify Product Uploader
by @hussainpatan9
Upload and manage Shopify products individually or in bulk with SEO-optimised titles, descriptions, tags, support for variants, collections, inventory, and d...
clawhub install shopify-product-uploaderπ About This Skill
Shopify Product Uploader v2
Overview
Upload single products, bulk CSV batches, or image-based products to a Shopify store. Automatically generates SEO-optimised titles, descriptions, and tags in UK English. Supports variants (size, colour), collections, multi-location inventory, draft mode, archiving, duplicate SKU handling, and barcode fields.Trigger Phrases
Activate this skill when the user says any of the following (or similar):Configuration (ask on first use if not set)
Before running any workflow, check that the following are configured in memory. If missing, ask the user once and store in long-term memory:SHOPIFY_STORE_HANDLE # e.g. my-store (not the full URL)
SHOPIFY_ACCESS_TOKEN # Admin API access token (starts with shpat_)
SHOPIFY_API_VERSION # Default: 2025-01 (update annually)
DEFAULT_VENDOR # Optional: store's default brand name
DEFAULT_CURRENCY # Default: GBP
DEFAULT_LANGUAGE # Default: en-GB
DEFAULT_STATUS # Default: active (options: active | draft)
SHOPIFY_LOCATION_ID # Fetched automatically on first inventory operation
Store these securely in memory under the key shopify_config.
Never log or repeat the access token back to the user.
Fetching location ID automatically
On first use of any inventory operation, fetch and store the primary location:GET https://{store}.myshopify.com/admin/api/{version}/locations.json
Store the first active location's id as SHOPIFY_LOCATION_ID.
If multiple locations exist, show the list and ask the user which to use as default.Workflow A β Single Product Upload
Use when the user provides details for one product via text message.
Step 1 β Extract product data
Parse the user's message for:If critical fields are missing (price, product name), ask in one message before proceeding. Do not ask for fields that can be inferred or generated (title, description, tags).
Step 2 β Generate SEO content
Generate the following in UK English:Title (max 70 characters):
Description (150β300 words, HTML formatted):
tags for paragraphs,
/ for specsTags (5β10 tags):
SEO meta title (max 60 chars) and meta description (max 155 chars):
Step 3 β Confirm before upload
Show a structured summary to the user:π¦ Ready to upload:Title: [generated title]
Status: [active / draft]
Price: Β£[price] GBP
Compare-at: Β£[compare_at] (if applicable)
Variants: [list]
Tags: [list]
Collection: [name or "None"]
Stock: [quantity] units
Barcode: [barcode or "β"]
Images: [count] attached / [URLs]
Description preview:
[first 100 chars of description]...
Reply YES to upload, or tell me what to change.
Do not upload until the user confirms with YES or equivalent.
Step 4 β Upload to Shopify
POST https://{SHOPIFY_STORE_HANDLE}.myshopify.com/admin/api/{SHOPIFY_API_VERSION}/products.json
Headers:
Content-Type: application/json
X-Shopify-Access-Token: {SHOPIFY_ACCESS_TOKEN}
Request body:
{
"product": {
"title": "{generated_title}",
"body_html": "{generated_description_html}",
"vendor": "{DEFAULT_VENDOR}",
"product_type": "{inferred_product_type}",
"tags": "{comma_separated_tags}",
"status": "{active_or_draft}",
"variants": [
{
"option1": "{variant_value}",
"price": "{price}",
"compare_at_price": "{compare_at_price_or_null}",
"sku": "{sku_or_empty}",
"barcode": "{barcode_or_null}",
"inventory_management": "shopify",
"inventory_quantity": "{stock_qty}",
"fulfillment_service": "manual",
"requires_shipping": true,
"taxable": true
}
],
"options": [
{ "name": "{option_name e.g. Colour}", "values": ["{variant_values}"] }
],
"images": [
{ "src": "{image_url}" }
],
"metafields": [
{
"namespace": "global",
"key": "title_tag",
"value": "{seo_meta_title}",
"type": "single_line_text_field"
},
{
"namespace": "global",
"key": "description_tag",
"value": "{seo_meta_description}",
"type": "single_line_text_field"
}
]
}
}
#### After product creation β set inventory per location Always run this step after creating a product, even for single-location stores:
POST .../inventory_levels/set.json
{
"location_id": {SHOPIFY_LOCATION_ID},
"inventory_item_id": {variant.inventory_item_id},
"available": {stock_qty}
}
This ensures inventory registers correctly on both single and multi-location stores.Step 5 β Report result
On success:
β
Product uploaded!Title: [title]
Status: [active / draft]
Admin URL: https://{store}.myshopify.com/admin/products/{id}
Store URL: https://{store}.myshopify.com/products/{handle}
Product ID: {id}
Inventory: [qty] units at [location name]
On 422 Duplicate SKU β offer to update instead:
β οΈ SKU "[sku]" already exists:
"[existing product title]" (ID: {id})Options:
1. Update the existing product with new data
2. Upload as new product with no SKU
3. Cancel
What would you like to do?
Workflow B β Bulk CSV Upload
Use when the user shares a CSV file path or pastes CSV data.
Step 1 β Parse CSV
Read the file from the provided path or parse pasted content. Accept flexible column naming β map common variants:If column mapping is ambiguous, show detected mapping and ask user to confirm. Store confirmed mapping in memory keyed by filename pattern for future reuse.
Step 2 β Pre-upload validation
Before showing the preview, validate:Step 3 β Preview
Found 47 products in products_may.csvColumn mapping:
product_name β title β | sell_price β price β | rrp β compare_at_price β
ref β SKU β | qty β inventory β | img_url β image β | category β collection β
Row Title Price Variants Images Status
1 Bamboo Cutting Board Β£18.99 S, M, L 1 β active
2 Linen Tea Towel Set Β£14.50 Natural/Grey 1 β active
3 Glass Cafetiere 600ml Β£24.99 β 1 β draft
β οΈ 3 rows missing images β will upload without images
β οΈ 1 row missing price (Row 12) β will skip
π 2 rows have existing SKUs (Rows 8, 23) β will UPDATE those products
β
No internal duplicate SKUs
Reply YES to proceed, or tell me to adjust specific rows.
Step 4 β Generate content in batches
For rows with missing or raw descriptions:For rows that already have a full description:
Step 5 β Upload sequentially
Uploading... 12/47 β
| 2 updated π | 0 failed βStep 6 β Final report
β
Bulk upload completeNew uploads: 42
Updated: 2
Failed: 2
Skipped: 1
Failed:
Row 31: Invalid image URL β uploaded without image β οΈ
Row 38: Shopify 503 timeout β retry this row Report saved: bulk_upload_report_{timestamp}.txt
Workflow C β Upload from Image
Use when the user attaches one or more product photos.
Step 1 β Analyse image
Use vision to identify:Step 2 β Ask for missing details (single message)
Step 3 β Generate and upload
Follow Workflow A from Step 2 onwards. If the image was attached as a file (not a URL), tell the user: "Please upload this image to Shopify Files or your CDN and share the URL β Shopify's API requires a public image URL."Workflow D β Add to Collection
GET .../custom_collections.json # list existing
POST .../collects.json # add to existing collection
{ "collect": { "product_id": {id}, "collection_id": {id} } }
POST .../custom_collections.json # create new if needed
{ "custom_collection": { "title": "{name}" } }
Workflow E β Update Existing Product
Triggered by: "update product", "change price", "edit listing", "add variant", "update stock"
Find product:
GET .../products.json?title={search_term}&limit=5
Common updates:
Price:
{ "product": { "variants": [{ "id": {id}, "price": "{new_price}" }] } }
Add variant:
POST .../products/{id}/variants.json
{ "variant": { "option1": "{value}", "price": "{price}", "sku": "{sku}", "barcode": "{barcode}" } }
Stock (multi-location aware):
POST .../inventory_levels/set.json
{ "location_id": {SHOPIFY_LOCATION_ID}, "inventory_item_id": {id}, "available": {qty} }
Description:
{ "product": { "body_html": "{new_html}" } }
Image:
POST .../products/{id}/images.json
{ "image": { "src": "{url}", "position": 1 } }
Workflow F β Draft Mode
For "upload as draft", "don't publish yet", "save for review":
Same as Workflow A but "status": "draft". Confirmation must clearly show DRAFT status.
To publish a saved draft: "publish [product name]"
PUT .../products/{id}.json
{ "product": { "status": "active" } }
Workflow G β Archive / Unpublish / Delete
Triggered by: "take down", "archive", "unpublish", "hide from store", "remove"
Archive (reversible): Always confirm first with a warning, then:
PUT .../products/{id}.json
{ "product": { "status": "archived" } }
Permanent delete (only if user explicitly says "delete permanently" or "delete forever"):
Confirm by typing DELETE β this cannot be undone.
DELETE .../products/{id}.json
SEO Content Rules
1. UK English always β colour, grey, aluminium, centre, organise, fibre 2. Sentence case titles β not Title Case For Every Word 3. Open descriptions with a benefit, not "This product is..." or "Introducing..." 4. Primary keyword in first sentence, naturally 5. Dimensions: metric first (cm/mm/kg), imperial in brackets if helpful 6. Prices in GBP (Β£) β never USD 7. No VAT mention unless user asks 8. No unsubstantiated superlatives ("best", "world-class") 9. No filler: "high quality", "perfect for", "look no further" 10. Safety-relevant products: add compliance note (CE, UKCA, food-safe) 11. Food/supplements: never make health claims not provided by the user 12. Clothing: always include care instructions if material is known
Error Handling
| Error | Cause | Action | |-------|-------|--------| | 401 | Invalid/expired token | Ask user to regenerate in Shopify admin | | 403 | Missing API scope | Name the exact missing scope | | 422 Duplicate SKU | SKU exists | Offer to update existing product | | 422 Missing field | Required field absent | Name specific field, ask user | | 429 Rate limit | Too many requests | Auto-retry after 2s, up to 3Γ | | 404 | Wrong store handle or ID | Verify handle, re-fetch product list | | 503 | Shopify outage | Wait 30s, retry once, then report |
Memory Instructions
Store after each session: