Vibe code your own Shopify

Vibe code your own Shopify as a catalog and a hosted checkout. Inventory holds, tax, fraud, and the app store are the other product. Here is the split.

  • teardown
  • vibe-coding
  • scope

You can vibe code your own Shopify. Start with a catalog and a hosted checkout. The catalog calls Shopify one storefront, not an ecommerce platform.

You pay $39–$399 a month. The verdict is “One catalog, one checkout”.

Earlier today I wrote about don’t rebuild Slack. That was a regular backlog post. This one is a teardown: what is public about the architecture, which half you can vibe-code, and which half you should leave alone.

Vibe code your own Shopify as a catalog and a checkout

The Shopify page splits the work into three rows.

The first row is 5 days. Catalog, cart, Stripe checkout, order list. That is products, a cart, a hosted payment page, and a dashboard of paid orders. You can vibe-code this.

The second row is 2 weeks and up. Variants, inventory reservation, fulfillment states, order emails. Still one country and one catalog. More screens, a hold that expires, a packing column.

The third row is 3 months and up. Tax engines, multi-currency, returns, fraud, an app store. Skip that row.

If you tell an agent to “clone Shopify”, it aims at the third row. The useful v1 is the first one.

Here is the shape of the data. Nothing else has to exist at 5 days.

const product = {
  id: 'mug-logo',
  title: 'Site mug',
  priceCents: 2400,
  inventory: 48,
  images: ['https://flaviocopes.com/mug.jpg'],
  active: true,
}

A product, a price in integer cents, a count, a photo in R2. Variants can wait until you sell two sizes of the same mug. Tax waits until you ship past one country.

The audience line on the page is a maker selling fewer than 50 physical products in one country. If that is you, a catalog and a hosted checkout are enough. If the shop already lives in Shopify’s admin, apps, and reports, keep paying.

The same v1 also replaces BigCommerce and Squarespace Commerce if what you wanted was a product list and a card form. Gumroad is the digital cousin at $10–$50 a month. Teachable is the course version at $39–$159. Carrd is the page if you only needed a landing URL.

How does Shopify work

Bart de Water’s QCon talk is the public map of the platform. I am summarizing that talk, not reconstructing their database.

Ruby on Rails is the monolith. MySQL, Redis, and memcached hold shop data. Go and Lua sit in the performance-critical bits. React and GraphQL serve the admin. They deploy the monolith around 40 times a day.

A Shopify pod is a complete copy of Shopify for a group of shops. Datastores stay isolated. Stateless workers are shared in a region, so a flash sale can borrow capacity without writing into another pod’s MySQL. A pod is active in one region and replicated to a second.

Shops do not talk to each other. A request or a job works on one shop. Code that needs every shop, like Shop Pay, lives in a separate app.

OpenResty sits at the edge. A Lua module they call Sorting Hat looks up the host and routes to the right pod.

Storefront is mostly reads. Checkout is writes plus calls to banks. They rewrote storefront rendering as its own Ruby app and shadowed HTML until it matched Liquid output. Treat “the 2026 storefront still renders that way” as an educated guess. Browse is a cache problem, and pay is a consistency problem.

Card data never enters the monolith. In that talk, checkout iframes post to CardSink, which encrypts the number and returns a token. A job hands the token to CardServer, which talks to the processor. Treat those two names as the talk’s names, not a 2026 org chart. The product rule on the card is the same: keep card data in hosted checkout.

A 5-day v1 does not need pods. One Worker, one D1 database, R2 for photos. The catalog’s stack is Astro, HTMX, and D1. Use that, or Rails if that is your weekday language.

CART CHECKS STOCK Price snapshotted in cents RESERVE ON PAY START Short hold, then expiry HOSTED CHECKOUT Card never hits your app Stripe or Shop Pay token WEBHOOK CLAIMS STOCK One event id, one paid order PACK AND SHIP Auditable state changes
The public path: hold stock, take payment off your server, claim on the signed webhook, then pack.

How Shopify inventory reservation works

Shopify’s May 12, 2026 post We replaced Redis with MySQL for inventory reservations is the current public picture of oversell protection. I am summarizing that write-up.

Reserve marks units when payment starts, for a short hold of several minutes. Claim deducts those units from the inventory ledger when payment succeeds. The ledger is the source of truth.

A bad reserve either sells the last mug twice or hides a unit that is still on the shelf.

The old Redis model stored a quantity key per item. Reserve was DECR. Release was INCR. The bug was two systems. Claim had to update MySQL and clean Redis, and those writes could not share one transaction. Payment could succeed without a claim.

They moved reservations into the same MySQL database as the ledger. One row per sellable unit. Ten mugs means ten rows. Reserving three mugs moves three rows in one transaction. SELECT ... FOR UPDATE SKIP LOCKED skips rows another checkout already locked.

A full row per unit for a 50,000-unit SKU would be huge, so they keep a bounded pool, capped at 1,000 rows per item and location. A replenisher refills it from the ledger.

On Black Friday 2025 their merchants peaked at $5.1 million in sales per minute. The same post says Shopify powers over 14% of U.S. ecommerce. You do not need that pool for 48 mugs.

The catalog’s first hard rule is the same product rule: reserve inside a transaction, expire the hold without double-releasing, and never let paid stock go below zero. The mini-prompt is “The last unit and the two shoppers who want it”.

async function startCheckout(db, cart) {
  const expiresAt = Date.now() + 15 * 60 * 1000
  return db.transaction(async (tx) => {
    for (const line of cart.lines) {
      const updated = await tx.run(
        `UPDATE variants
         SET available = available - ?
         WHERE id = ? AND available >= ?`,
        [line.qty, line.variantId, line.qty]
      )
      if (updated.changes !== 1) {
        throw new Error('out-of-stock')
      }
    }

    return tx.insertReservation({
      cartId: cart.id,
      expiresAt,
    })
  })
}

A 5-day v1 can use a quantity column. Two shoppers, one unit, one winner. The loser gets the cart back. Shopify’s row-per-unit design is for the day that column becomes a single hot lock.

A sweeper releases expired holds. Running it twice must not add the stock back twice. Completing payment at the last second must convert or fail, never both. States help: active, converted, released.

Snapshot the name, price, and shipping onto the order lines when checkout starts. The mini-prompt is “The order that remembers what the price was”. A later price edit must not rewrite the receipt.

How Shopify tax and fraud work

Chris Inch and Vignesh Sivasubramanian’s Componentizing Shopify’s Tax Engine (July 5, 2019) is why tax is its own product. Product type, buyer location, shipping origin, and nexus all change the number. Cart, checkout, and order creation each used to call tax their own way. They boxed it behind one request schema, then ran old and new side by side until the cents matched.

Shopify’s tax app docs still treat calculation as an HTTP POST with cart, customer, and delivery details. Help Center tax rounding now rounds at the line. I would not rebuild that. The catalog’s non-goal already names complex tax. One country, or prices that already include what you owe.

Fraud is the network. Shopify’s help page Reviewing orders with fraud analysis says recommendations come from machine learning trained on historical transactions across all Shopify stores. Low, medium, or high risk of a fraud chargeback. Shopify Payments can block some high-risk attempts before an order exists. A 2023 Shopify post, Protecting millions of merchants from fraud, claims the model trained on over 10 billion transactions. That is their number.

A vibe-coded shop of 50 SKUs has no such graph. Hosted checkout plus 3D Secure is the protection you can buy. Stripe’s Checkout is a hosted page or an embedded form. Card numbers stay on Stripe. Webhooks arrive at least once. Verify the signature on the raw bytes. Deduplicate the event id. Mark paid in the same write that records that id.

The mini-prompt is “The webhook that arrives three times”.

async function onPaid(db, req, secret) {
  const raw = await req.text()
  if (!validStripeSignature(raw, req.headers, secret)) {
    return { ok: false, reason: 'bad-signature' }
  }

  const event = JSON.parse(raw)
  const inserted = await db.tryInsertEvent(event.id)
  if (!inserted) {
    return { ok: true, duplicate: true }
  }

  await db.markPaid(event.orderId)
  await enqueueConfirmation(event.orderId)
  return { ok: true, duplicate: false }
}

Acknowledge the duplicate. Send the email after the paid write commits, keyed by order id. An unknown order is a log line and a 200, not a crash.

Refunds are a second two-system problem. The mini-prompt is “Refunded means the money actually moved”. A timeout is not a refund. Stay pending and reconcile against Stripe’s refund list with the idempotency key you sent.

The app store is the other product

Shopify’s app platform is how merchants bolt on the missing piece. Your app runs inside their admin, checkout, and storefront. Shopify hosts the extensions. You host the backend. Checkout customization lists Functions, UI extensions, and web pixels. App Store review is a second product: privacy rules, no unapproved payments, no circling around checkout.

That marketplace is why a Shopify shop can add shipping labels, subscriptions, and reviews without writing them.

The catalog’s skip row is that platform. Do not start an app directory. Do not calculate nexus. Do not train a fraud model on twelve test orders.

A field for tracking text is enough fulfillment. Paid, packing, shipped, refunded, cancelled. Write each change to a log.

A Shopify alternative for fifty products

5-DAY V1 Catalog and cart Hosted checkout Paid order list SKIP THIS HALF Tax engine and nexus Fraud across every shop App store and returns
Ship the catalog and the hosted checkout. Leave tax, fraud, and the app directory on the skip row.

I would rebuild Shopify for a maker who currently pastes Stripe payment links into a Carrd page and tracks stock in a sheet. Forty-eight mugs, one country, one flat shipping rate. Photos in R2. Secrets in a separate table. Orders with snapshotted lines. A 15-minute hold so two browsers cannot buy the last mug.

I would host it next to the product, on Astro, HTMX, and D1, because that is the stack on the card. Use Rails if that is your weekday language. Add variants once a real SKU needs a size. I would not build a theme editor, a tax engine, or an app store.

If the rent is a file and a download link, that is Gumroad, $10–$50 a month, 12 hours for product pages, checkout, and signed delivery links. If the rent is a course, that is Teachable. If twelve apps already fulfill, tax, and restock the shop, keep paying.

Zapier is often the glue after the order lands. How Zapier works is that split. A form that starts a waitlist before the drop is a Typeform replacement.

Start with the first row

Go to Build your own Shopify. Copy the AHA prompt, or the stack you already write.

Tell the agent the first estimate only: catalog, cart, Stripe checkout, order list. Five days.

If the company already lives in the admin and the app store, keep paying.