DEV Community

RAXXO Studios
RAXXO Studios

Posted on • Originally published at raxxo.shop

Shopify Admin API From Claude: 5 Patterns I Use Across 15 Repos

  • Product create via Admin API beats clicking through the dashboard every time

  • Blog post POST with metafields saves 12 minutes per article

  • Metaobject sync keeps 15 repos consistent without manual edits

  • Staged uploads handle file assets that the simple API rejects

I stopped using browser automation for Shopify two years ago. Every store task I run now goes through the Admin API, called from Claude. Across 15 repos that touch my Shopify store, five patterns cover roughly 90 percent of what I need.

Why I Killed Browser Automation First

The case against clicking through the dashboard is simple. I tried Puppeteer for store admin in early 2023. It broke 4 times in 6 weeks. Shopify ships UI changes constantly, and every layout shift breaks a selector. A button moves, a modal gets a new wrapper div, and the whole flow dies at 2am while I am asleep.

The Admin API does not move. The 2024-10 endpoint behaves the same today as the day it shipped. When Shopify deprecates a version, I get a date months ahead, not a silent failure. That alone saved me an estimated 30 hours of debugging in 2024.

Here is the math on a single task. Creating a product through the UI: open admin, click Products, click Add product, fill 9 fields, set variants, upload images, hit save. About 4 minutes if I know exactly what I want. Through the API from Claude: one structured request, 8 seconds. Multiply that across 200-plus products and the difference is not small.

The real win is repeatability. A browser script encodes "where the button is." An API call encodes "what I want to happen." The second one survives redesigns. When I ask Claude to create a product, it builds the mutation, I review it, and it runs. No headless Chrome, no flaky waits, no screenshots to debug.

I keep a single auth pattern across all 15 repos. One private app token per store, scoped to exactly the permissions that repo needs. The blog-posting repo gets write_content and nothing else. The product repo gets write_products. If a token leaks, the blast radius is tiny. Claude reads the scopes from an env file and never sees the raw secret in chat.

If you want the full reasoning behind running everything through Claude, Claude Blueprint lays out the setup. The short version: APIs are contracts, and contracts are what you automate against. UIs are surfaces, and surfaces are what you click when you have no other choice.

Pattern One: Product Create With Variants

Product creation is the workhorse. I run it through the GraphQL productCreate mutation, not REST, because GraphQL lets me set the product, its options, and its first variant in one round trip. REST needs two or three calls and a follow-up for variant pricing.

The structure I give Claude is always the same shape. Title, descriptionHtml, productType, vendor, tags as an array, status set to DRAFT until I confirm, and a variants block with price, SKU, and inventory policy. I keep status as DRAFT on purpose. A product that goes live before I check the images embarrasses me publicly.

The piece people get wrong is options versus variants. If a product has one color and one size, you still declare the option. Shopify rejects a variant that references an option value that does not exist on the parent. Claude handles this once you give it the rule: every variant's optionValues must map to a declared productOptions entry. I had this fail 6 times before I wrote the rule into my repo's instructions file.

For pricing I never hardcode numbers in the mutation. I pass them as variables. A typical product sits at 29 EUR or 39 EUR depending on the line, and keeping those in a config means I can adjust a whole catalog with one find-and-replace instead of editing 40 mutations by hand.

Image attachment is a separate step, and that is where Pattern Five (staged uploads) comes in. The productCreate call returns a product ID. I hand that ID to the file flow and attach media afterward. Inlining a large image as base64 works for small files and fails for anything over a couple hundred kilobytes. Keep them separate.

One detail that saved me real time: I always request the userErrors field. GraphQL returns a 200 status even when the mutation logically failed. Without userErrors you think it worked. With it, Claude reads back exactly why the variant got rejected and fixes the payload on the next attempt. That feedback loop is the entire reason this is faster than the UI.

Pattern Two: Blog Post POST And Metafields

Every article on my store, including this one, gets posted through the Admin API. The blog content endpoint takes a blog ID, a title, body HTML, an author, tags, and a published date. I never paste into the rich text editor. It reformats my HTML in ways I do not approve, and it strips attributes I want to keep.

The flow Claude runs: fetch the list of blogs to get the right blog ID, then POST the article. I cache the blog ID in config so the fetch only happens once. Posting a finished article takes about 6 seconds versus the 12 minutes it used to take me to paste, fix formatting, set the excerpt, add tags, and publish.

Metafields are where this gets useful. After the post lands, I attach metafields for the TLDR data, the canonical handle, and the syndication targets. These drive my theme's custom blog template. Setting them through the API means every article is structured identically. No drift between post number 3 and post number 80.

The trap with blog metafields is the namespace and key. They must already exist as a definition or the value saves but never shows in admin and sometimes does not render in Liquid. I define them once in store settings, then reference the exact namespace and key in every POST. Claude keeps these in a constants block so I cannot fat-finger them.

For scheduling the social posts that announce each article, I push the published URL into Buffer through its own API. The Shopify side handles the canonical content, Buffer handles distribution. Keeping those systems separate means a scheduling failure never touches my published article.

I also validate the body HTML before posting. Shopify's content endpoint accepts almost anything, which is dangerous. A stray unclosed tag can break the page layout. Claude runs a quick parse check first and flags mismatched tags. This caught 3 broken articles before they went live last quarter. Cheap insurance for a 6-second operation.

Pattern Three: Metaobject Sync And Template Validation

Metaobjects are the feature I underused for too long. They are structured content entries you define yourself: a "studio fact," a "product benefit," a reusable callout. I sync them across repos because the same metaobject powers content on multiple pages.

The pattern is a read-then-write reconcile. Claude queries existing metaobjects of a given type, compares them against my source-of-truth list, and only writes the ones that changed or are missing. This matters because metaobject create will happily make a duplicate if you do not check first. I learned this after ending up with 11 copies of the same entry. Now the reconcile step is non-negotiable.

Each metaobject field has a type, and the validation is strict. A field defined as single_line_text_field rejects a value with a newline. A url field rejects anything that is not a valid URL. Claude reads the definition first, then formats each value to match. This front-loads validation so I never get a batch of half-written entries.

Page template-suffix validation belongs here too. When I create or update a page, I set its template_suffix to point at a custom Liquid template like page.studio for page.studio.liquid. The API does not check whether that template file exists. If the suffix points at nothing, the page renders with the default template and looks wrong. So before setting a suffix, Claude lists the theme's template assets and confirms the file is there. If it is missing, it stops and tells me instead of shipping a broken page.

This validation step is the difference between automation I trust and automation I babysit. I covered API contracts in the opening, and metaobjects are the cleanest example. The definition is the contract. Validate against it, and the writes are safe. Skip validation, and you spend an afternoon cleaning up 11 duplicates and a page that quietly renders wrong.

I run this sync across all 15 repos with the same code, parameterized by metaobject type. One function, fifteen callers. That is the payoff of treating Shopify as an API instead of a website.

Pattern Four: File Upload Via Staged Upload

File upload is the one that defeats most people, so it gets its own pattern. You cannot just POST a 2MB image to the product media endpoint and expect it to land. Shopify uses a staged upload flow, and it is three steps, not one.

Step one: call stagedUploadsCreate with the filename, mime type, and file size. Shopify returns a temporary upload URL and a set of parameters. Step two: PUT the actual file bytes to that URL with the exact parameters Shopify gave you. Step three: take the resource URL from step one and reference it in productCreateMedia or fileCreate to register the file.

The mistake everyone makes is on step two: those parameters are not optional decoration. They are signed values the upload target validates. Send the file without them, or reorder them, and you get a cryptic 403. Claude builds the multipart form in the exact order Shopify specified, and the upload works first try.

I use this for product images, blog hero images, and downloadable assets. For images I generate or upscale through Magnific before they touch the upload flow, because Shopify stores whatever you give it, including a low-res file that looks bad on a retina display. Upscale first, upload second.

Size matters for the staged flow. Files under 20MB go through one PUT. Larger files need multipart with parts, which is rarely worth it for store assets. I keep my images under 2MB after compression, so I never hit the multipart path. Simpler is better.

One thing that saved me repeated grief: poll the file status after registering it. Shopify processes uploaded media asynchronously. The fileCreate call returns immediately, but status goes UPLOADED then READY. Attach a file that is still processing and check it instantly, and it looks broken. Claude polls until status is READY, with a 5-attempt cap so it never hangs. That cap caught one genuinely stuck upload last month instead of looping forever, which is exactly what a browser script would have done.

Bottom Line

Five patterns cover almost everything I do with Shopify: product create, blog POST with metafields, metaobject sync, template-suffix validation, and staged file upload. None of them touch a browser. All of them run from Claude against the Admin API, which means they survive every dashboard redesign Shopify ships.

The shift that mattered was treating Shopify as a contract instead of a website. A contract you validate against and automate confidently. A website you click through and pray nothing moved. Across 15 repos, the same auth pattern, the same reconcile logic, the same validation steps. Write a function once, parameterize it, call it everywhere.

If you are setting this up yourself, start with product create and the userErrors field, because that feedback loop teaches you how the API thinks. Then add staged uploads, the only genuinely tricky one. The full workflow is in Claude Blueprint. Build the contract once, and the store mostly runs itself.

This article contains affiliate links. If you sign up through them, I may earn a small commission at no extra cost to you. (Ad)

Top comments (0)