Case study · Headless Shopify

DreamDrop Gardens

We kept an existing Shopify store as the backend and built a new plant-nursery brand in front of it — with seasonal pre-orders, deposits charged on Stripe, and plant data a gardener can actually filter by.

  • Remix · React 19
  • Shopify backend
  • Stripe deposits
  • Plantaerium data
~1,500

Plant listings from several wholesale growers

50%

Deposit at checkout, balance charged when the plant ships

2 → 1

Two brands running on one Shopify store

3

Environments — Test, Staging, Production — deployed from GitHub

At a glance

Brand
DreamDropGardens.com — a specialty nursery for roses, clematis, hydrangeas, wisteria, Japanese maples, and other shrubs, vines, and trees
Parent store
Realmdrop.com, a general-merchandise Shopify store
Approach
Headless: the existing Shopify store stays the system of record; a new custom storefront sits in front of it
Front end
Remix, React 19, TypeScript, Tailwind CSS
Commerce
Shopify — products, inventory, orders, customer accounts, email
Payments
Stripe — deposits, authorize-then-capture, saved-card balances
Plant data
Plantaerium, Realmdrop's plant reference and supplier catalog
Phase 1 scope
Browse, pre-order, pay, fulfill, and manage — end to end
The storefront homepage — pre-orders front and center.

Where we started

A department store that outgrew its theme.

Realmdrop.com was meant to be a department store — many departments under one roof, on a standard Shopify theme. As the catalog grew, the theme's limits shaped the store more than we did.

No real mega menu

Shopify navigation is a simple nested list. With dozens of departments, shoppers couldn't browse — only search.

Scattered search

A search for a rose might return a garden hose, a planter, and a gift item, with little control over ranking or grouping.

Shallow filtering

Price and availability, yes. Hardiness zone, bloom colour, mature size, or thornless canes needed app configuration — and still fell short.

One template for everything

A plant, a phone case, and a kitchen tool shared one product page, with no room for what a gardener needs.

Most of Realmdrop's volume came through marketplaces like Amazon and eBay, and most on-site sales came from Google and Bing Shopping ads. Shoppers landed on one product, bought it, and left. Few people browsed the store as a destination.

The garden didn't fit

Plants run on seasons. Shopify runs on stock.

  • Most plants are sold before they exist in shippable form — bareroot roses, clematis starters, and wisteria are reserved months ahead.
  • Shopify has no real pre-order model. A product is in stock or it isn't, and “oversell” applies globally, with no idea of seasons or allocations.
  • Charging in full for a plant that ships in six months invites disputes — and a short harvest means refunds plus processing fees we never got back.
  • Invoice-later didn't work either: a large share of invoices went unpaid, after we'd held stock for those customers.
We wanted customers to commit with money at order time — while everyone understands fulfillment happens later.
The DreamDrop Gardens logo: a white hummingbird-and-rose emblem above the DreamDropGardens wordmark

The decision

Split the garden into its own brand — and keep the backend.

Instead of forcing the garden department into Realmdrop's theme, we gave it its own brand, DreamDrop Gardens, with a storefront built from scratch.

We deliberately kept the same Shopify store behind it. Products, inventory, orders, and customers stayed where they were, and every sales channel already connected to that store kept working.

Goals

Seven things the new storefront had to do.

Built for plants

Browse by family, species, type, and attribute, with filters a gardener cares about.

A true pre-order program

Seasonal programs, stock caps per variety, and clear ships-now vs. ships-later messaging.

Payment that matches fulfillment

Deposit or pay in full at checkout; balance at ship time; review before money moves.

One backend, two brands

Shared store, inventory, and order history — without either brand's rules leaking into the other.

Clean, correctable plant data

Kept outside Shopify, so bad supplier data is fixed once and pushed everywhere.

Fast and accessible

Mobile first, with a catalog of more than a thousand products.

A reusable template

Another store can be stood up through configuration, not a fork.

Architecture

Shopify for what it does well. Our own layer where the business differs.

The storefront is a server-rendered web app that handles everything a shopper sees: browsing, filtering, search, cart, checkout, and accounts. A staff admin panel lives in the same app behind its own login.

Shopify stays the system of record and serves every image from its global CDN. Stripe handles cards. Only the staff admin talks to Plantaerium — public pages read a copy already synced into Shopify.

It runs in Docker on our own server, with Test, Staging, and Production deployed automatically from GitHub.

Shopper

DreamDrop storefront

Custom server-rendered React app + staff admin

  • Browse & filters
  • Cart & checkout
  • Accounts
  • Admin panel

Shopify

  • Products
  • Inventory
  • Orders
  • Customers
  • Email
  • CDN

Stripe

  • Deposits
  • Holds
  • Captures
  • Saved cards

Plantaerium

  • Plant reference
  • Supplier catalogs
Webhooks push product, inventory, and payment changes back to the storefront.

Challenge 01 · Pre-orders

We built the pre-order model twice before it fit.

Shopify has no idea of seasons, allocations, or deposits. Our first model fought that; the second one worked around it.

Attempt 1Retired

Incoming inventory as pre-order stock

“Available” meant ships now; “incoming” meant this season's allocation. Great for shoppers — but Shopify's cart needs “continue selling when out of stock,” and that setting is global. Pre-order plants became buyable, uncapped, on every other channel: Realmdrop.com, Google Shopping, Shop, Facebook.

Attempt 2What we run

Our own cart, draft orders, real orders at ship time

Every variant is set to “do not oversell” on every channel. The DreamDrop cart knows the pre-order rules and prices through Shopify's draft-order calculation, so tax, discounts, and shipping still come from Shopify. Pre-orders are storefront-only by design.

The redo inside the redoPatched in production

Pay-in-full pre-orders stay as drafts too

Shopify won't complete a draft for stock that's only incoming — we learned that when the first pay-in-full pre-orders failed after the card was authorized. They now wait as drafts like deposits do, and any failure at that step alerts the owner.

The opposite failure happened too: products imported without the oversell setting couldn't be added to a cart at all. One morning, more than 300 newly opened clematis pre-orders were quietly unbuyable until we caught it.

How a pre-order moves today

  1. 1

    Cart

    Our own cart checks each line against the live allocation.

  2. 2

    Draft order

    Checkout creates a Shopify draft — not a real order.

  3. 3

    Authorize

    Stripe authorizes the deposit or full amount and saves the card.

  4. 4

    Review

    The owner confirms the harvest before money moves.

  5. 5

    Capture

    The hold is captured — or released if the harvest falls short.

  6. 6

    Ship

    The draft becomes a real Shopify order and the balance is charged.

Challenge 02 · Payments

Deposits, holds, and balances — on Stripe.

Shopify's checkout couldn't express what we needed, so the storefront manages payments on Stripe directly.

Deposit or pay in full

Pay 50% now and the rest at ship time, or pay everything up front.

Card saved for the balance

With consent at checkout, the card is kept on file. No ignored invoices, no re-entering a card months later.

Authorize now, capture after review

If a harvest falls short, the hold is released rather than refunded — no charge-and-refund for the customer, and far fewer lost fees. Configurable per order type.

Watch the clock

Authorizations expire after about seven days, so the admin panel surfaces holds close to expiring.

Linked both ways

Each Stripe payment records its Shopify order, and each order records its payment.

Owner alerts

A phone and email alert the moment a pre-order deposit lands.

Fixed along the way

  • A mixed cart was silently split into two orders under one charge. Checkout now asks the shopper to choose, and the summary always equals the charge.
  • A shipping rate quoted for a large cart could survive after the cart shrank. Rates are re-quoted before payment.
  • A balance could be charged twice from two tabs. Balance charges are now one attempt per order, and Stripe is checked first.
Pre-order checkout: ship window, terms, and deposit or pay in full.

Challenge 03 · Plant data

Supplier data is messy. Fix it once, upstream.

Every grower sends a catalog in its own format, with its own naming and occasional errors — we once found a vine sold under the wrong genus at the source. Plantaerium, our plant reference system, sits between suppliers and the store.

  1. Step 1

    Ingest

    Supplier catalogs go into Plantaerium, not Shopify. Duplicates across growers merge into one plant record.

  2. Step 2

    Correct once

    Fixes — a wrong genus, a missing bloom colour — are overrides with a recorded reason.

  3. Step 3

    Sync

    A resync pushes the fix to every affected listing, and derives flags like cold-hardy, fragrant, and evergreen.

  4. Step 4

    Serve

    Public pages read the synced copy in Shopify. If Plantaerium is down, the store is unaffected.

The separation paid off repeatedly. When a caching bug left stale attributes on hundreds of rose listings, the fix was a resync, not a manual cleanup. Bloom colour for nearly 400 flowering plants and foliage colour for about 330 more went into Plantaerium once and flowed to the storefront.

Facets built from Plantaerium data — bloom colour, hardiness zone, fragrance, breeder — with live counts.

Challenge 04 · Images

The hardest area — and the most personal.

Early on, a full sync set to “overwrite” replaced media on products that already had the owner's photography, and more than a hundred photos were removed. We rebuilt the policy: image sync is additive only. It adds photos where there are none and never deletes anything.

Our own photos are branded, not replaced. A watermark pipeline stamps the DreamDrop logo on nursery photos and uploads them to the matching listing — about 1,500 listings so far. Originals are detached, never deleted.

A nursery photo, branded by the watermark pipeline.

Field notes

Everything else a theme used to handle.

Going headless means owning the parts Shopify's theme and hosted checkout quietly covered. Open any note for the details.

A custom admin panelStripe and Shopify don't talk to each other, so staff got one panel for both.
  • Capture, release, charge the balance, refund, and cancel — a cancel reverses Stripe first, then Shopify.
  • Tabs for each stage: awaiting capture, deposits paid, balances due, shipped, cancelled.
  • Open, cap, close, or mark a pre-order program ready to ship, season by season.
  • Data audits, Plantaerium sync, sales-channel checks, and a one-click storefront cache refresh.
  • Guest order lookup and a customer-facing “My pre-orders” page.
Two brands, one order listEvery DreamDrop order is tagged by brand, site, and payment state.
  • One store behind two brands and three DreamDrop environments meant every order landed in one list.
  • The admin shows the current site's orders by default, with a switch to see everything.
  • Existing orders were backfilled with the same tags.
Emails: tags yes, attributes noDesign around what the email engine can read, not what the API can write.
  • Shopify's draft-order invoice email can't read custom attributes — only tags and the custom message.
  • The storefront writes brand, pre-order, and paid-amount facts as tags.
  • Templates detect DreamDrop orders by tag and switch branding; Realmdrop orders keep their own look.
Shared-store side effectsSharing a backend was the right call — with a few leaks to plug.
  • Realmdrop's free Economy rate was shipping a $40 plant order free. The storefront now decides which rates to offer.
  • Plants got their own delivery profile with flat rates, and every plant got a shipping weight.
  • Collections silently showed nothing when products weren't published to the headless channel — every collection was audited.
  • Custom plant metafields came back empty until their definitions allowed storefront access.
Search, filters, and navigationThe original reason for leaving the Liquid theme.
  • Mega menus — Shop by Species and Shop by Attribute — for each plant family, every link a live filtered page.
  • Shrubs, trees, and vines are assigned automatically from the genus. New imports land in the right place.
  • Shopify silently ignores filters not configured in Search & Discovery, so the storefront builds its own index and facet counts.
  • One product card everywhere: search, collections, wishlist, rails, and recommendations.
Webhooks keep everything in stepShopify and Stripe push changes; nothing polls.
  • Shopify → storefront: product and inventory updates keep caches, badges, and filters current.
  • Stripe → storefront: authorized, captured, failed, or cancelled payments update state and alert the owner.
  • Every webhook is signature-verified, and bursts are queued so the store isn't hit hundreds of times in a row.
  • Moved to Shopify's current app model with short-lived, auto-refreshing credentials when legacy custom apps were retired.
SecurityOwning checkout means owning what Shopify's hosted checkout used to cover.
  • Prices, stock, and pre-order status are always decided on the server. Nothing the browser sends changes the charge.
  • Session and cart cookies are encrypted, not just signed.
  • Rate limiting on login, lookup, and contact forms, keyed to an address the client can't spoof.
  • A strict Content Security Policy, and staff access separate from customer accounts.
  • Deploys fail loudly — after we caught Test silently serving a two-day-old build.
Learned the hard wayThe small things that make a storefront feel finished.
  • A missing viewport tag made early mobile pages look like shrunken desktop pages; checkout forms zoomed on iPhone. Every page is now checked at phone, tablet, and desktop widths.
  • Placeholder testimonials were replaced with real, attributed reviews from marketplace history.
  • Passwordless Shopify customer accounts, offered to guests after checkout with their email pre-filled.
  • Canonical URLs, Open Graph cards, sitemaps, and structured data on every public page; staging is blocked from indexing.
The staff admin hub — imports, Plantaerium sync, pre-orders, and payments in one place.
Mobile product page with ship date and zones.

Performance at catalog scale

From a few dozen pre-orders to more than a thousand in weeks.

The first design didn't scale with the catalog. We leaned on Shopify's CDN instead of running image servers, streamed pages instead of blocking on a stock scan, and let webhooks clear caches per product.

~8 MB
Resized

Homepage banners moved to Shopify's CDN, resized and converted on request

~2.4 MB
~65 KB

Above-the-fold image weight on the admin pre-order table

Cold start
~9×

Faster stock-index scan on cold start — and pages now stream

Hidden
144 back

Pre-order products recovered from a paging bug in a 1,100+ product collection

Figures measured during the build on the Test environment.

Solutions summary

Every Shopify limit, and our answer to it.

No pre-order model; “continue selling” is global across channels“Do not oversell” everywhere; pre-orders sold only through our cart as draft orders, converted at ship time
Checkout can't take deposits or charge balances laterStripe deposits, authorize-then-capture, saved card for the balance
Unpaid invoice-later requestsMoney committed at checkout; balance charged automatically at ship time
Harvest shortfalls meant refunds and lost feesAuthorizations released, not refunded
Stripe payments invisible in Shopify adminA staff admin panel across both systems, payments linked both ways
One store, two brands, three environmentsEvery order tagged by brand, site, and payment state
Notification emails can't read order attributesBrand and payment facts written as tags; templates branch on tags
No mega menu, weak search, limited filtersCustom navigation, plant-family collections, our own filtering engine
Messy, inconsistent supplier dataPlantaerium as the source of truth, with overrides and resyncs
Heavy images and slow pagesShopify CDN resizing, responsive images, additive-only image sync
Scale — 1,000+ pre-order productsStreaming pages, background-refreshed caches, webhook-driven updates
Shipping rates shared across brandsStorefront-controlled rate eligibility and a plant-only delivery profile

Store updates

Still growing, season by season.

Launch was the start. We keep updating and redesigning the live store as the garden year turns — here's each pass, newest first.

The October homepage on desktop and phone: “Grow a gothic garden, or a moon garden” over polaroid photos of Black Baccara, Pillow Talk, Black Magic and moon white blooms, with a 28-days-left countdown

October 2026

Gothic & moon garden

October's campaign: near-black roses and burgundy foliage for a gothic garden, white blooms that glow at dusk for a moon garden. Polaroid plant cards, price tags, and a countdown of days left in the month — and a campaign preview that lines up the season ahead: autumn pre-orders, October, almost winter, gifts & wreaths, Valentine's.

Phase 1

Running end to end.

A shopper can browse the catalog, reserve a plant, pay a deposit or in full, and get a branded receipt. Staff can review, capture, fulfill, and cancel from one panel, and the owner is alerted when money arrives. Phase 1 was about correctness and reliability — polish comes next.

What comes next

  • UI/UX polish

    Richer plant detail, a garden inspiration gallery, and brand-palette refinements.

  • Analytics

    View → cart → checkout → deposit → balance funnel tracking, plus ad attribution.

  • Dashboards

    Demand vs. allocation by variety and season, balances, expiring holds, shortfalls.

  • Automated fulfillment

    Batch balance charges and draft-to-order conversion when a season ships.

  • Marketplace alignment

    Keep Etsy and other channels consistent with the storefront catalog.

  • More stores

    Onboard additional stores on the same template.

Difficulty & our recommendation

Headless Shopify isn't a theme change.

You take over the cart, checkout, payments, caching, image delivery, SEO, accessibility, security, hosting, and deploys — and you meet Shopify's hard edges: global inventory policies, emails that can't see certain data, silently hidden metafields, silently ignored filters, and a draft order that refuses to become an order.

DifficultyHigh

For a simple store

Probably not.

In-stock products and a normal checkout? A good Shopify theme gets you to market faster and cheaper, and Shopify maintains it for you.

For a complex ordering model

Yes — real value.

Seasonal pre-orders, deposits, authorize-then-capture, per-variety caps, two brands on one backend, and specialist plant data are things a theme can't express. Keeping Shopify meant we didn't rebuild inventory, orders, accounts, tax, email, or the CDN.

Use Shopify for what it does well, and build your own layer only where your business is different. For DreamDrop Gardens, that layer is the reason the business works.

Outgrowing your Shopify theme?

Keep your backend. Build the part that's yours.

If your ordering model doesn't fit a stock theme — pre-orders, deposits, multiple brands, specialist product data — let's talk about what a custom storefront could look like.