Custom storefronts
Fast, accessible buying experiences without fighting the platform’s strengths.
Commerce / Shopify
Drag or use arrow keys to orbit
Shopify development
Custom storefronts, apps, and integrations that fit the operating model behind the checkout.
What we build
Fast, accessible buying experiences without fighting the platform’s strengths.
Purpose-built workflows connect catalogue, inventory, fulfilment, and support.
Theme and app work stays maintainable through upgrades and seasonal change.
The route to production
We map the real workflow, risks, users, and success measure before choosing an implementation.
Working software lands in visible increments, with the hardest assumptions tested first.
We validate against production-shaped data, failure paths, performance, and real use.
Deployment, monitoring, documentation, and a clean handover are part of the build.
Verified client reviews
“Rapid and great work, with high technical knowledge. Highly recommend.”
“I recommend him due to his thoroughness and quality.”
Before you ask
Probably not a public one. An app created inside the merchant's own admin hands you an Admin API token in minutes, but it cannot be embedded, cannot use App Bridge and cannot ship a single extension. The moment you need UI inside the admin, a checkout extension, a Function or a theme app block, you need an app created in the Partner Dashboard — and setting its distribution to custom installs it on exactly one store through a link, with no App Store review. One client sat in review for eleven weeks for an app that synced one merchant's ERP; switching them to custom distribution took an afternoon.
In three of its four layers, and the fourth is fenced by design. Presentation and merchandising belong in the theme, which is the cheapest ground you will build on. State and logic belong in an app. Decisions inside Shopify's own cart maths are Functions. Checkout is a walled garden of defined extension points. The same sentence in a requirements document can be three days in the theme, three weeks as an app plus a Function, or a plan question at checkout, so sorting requirements into those layers before quoting is most of the estimate.
Because Functions are sealed. They compile to WebAssembly and execute inside Shopify's own request path with no network access, a hard instruction budget and an input contract you declare as a query against the cart. Pricing that depends on a live call therefore cannot be computed while the buyer waits. You precompute it, write it to a metafield Shopify can already see, and let the Function read it. Pulling that decision forward is the single most common rework we do on Shopify projects.
The rate limiter, and it does not count requests. The GraphQL Admin API meters calculated query cost against a leaky bucket, so a query asking for 250 of anything with nested objects underneath can be rejected outright rather than throttled. Blind exponential backoff makes it worse, because the bucket is per shop: three workers backing off together wake up together and drain it again. We read the throttle status on every response and use bulk operations past a few thousand records — one nightly product sync went from six hours with regular limiter trips to about twenty minutes.
They are at-least-once, out of order, and occasionally absent. Deduplicate on the webhook ID header with a unique column, compare the payload timestamp against what you already hold and drop anything older, and verify the HMAC over the raw request bytes before any JSON parsing — a framework that re-serialises the body breaks the signature check only for payloads carrying non-ASCII characters, so the bug reproduces solely for customers whose names have accents. Shopify also retries a failing endpoint for roughly 48 hours and then deletes the subscription with no email, which is the mechanism behind an app that stopped syncing sometime last week.
Yes. Webhooks are the fast path and the reconciler is the correct path. Every Shopify integration we run in production has both: a nightly job that re-reads everything updated since the last cursor and repairs the difference, so a dropped event or a deleted subscription becomes a delay rather than a silent divergence between your systems and the store.
You can, and the plan has an expiry date printed on it. Script tags leave orphaned script calls in the theme after uninstall, which merchants find months later and blame on whoever touched the theme last, and checkout.liquid has already been retired in favour of checkout UI extensions. Theme app extensions are the replacement: the app ships a block, the merchant drags it into the theme editor where they want it, it is versioned alongside the app, and it disappears cleanly on uninstall.
It is the riskiest line in half the Shopify integrations we inherit, and the flow that produced it is gone. Since the start of 2026, new custom apps are created in the Dev Dashboard and the credential is requested at runtime rather than pasted anywhere. Apps installed on stores your own organisation owns use a client credentials grant that returns a token valid for about 24 hours, so code treating the token as fixed configuration works on the first night and returns 401 on the second. Token acquisition belongs behind one function that refreshes ahead of expiry.
As a release, not a checkbox. A new scope ships in a new app version and the store has to approve the added access before any request that needs it will work. We saw a team ship a feature and the scope it required in the same deploy, then spend an afternoon on access-denied errors from a query that was correct. Deploy the scope, confirm the grant, then ship the code that depends on it — and request only the scopes you can justify line by line, because security reviewers at larger merchants read those lists and the narrowest list gets approved fastest.
Yes, and there is no need to rush it, because legacy admin-created apps still run. We build the new app alongside the old one with identical scopes, install it on a development store first, move one job at a time onto the new token while the legacy token still serves everything else, and move webhooks last — deduplicating on resource ID and timestamp rather than delivery ID, because each event arrives once per app while both are subscribed. The migration is finished only when a request made with the old token returns 401.
From the journal
Start with a paragraph, a Figma, or a Loom.
Build this properly.Reply within one business day · one clear scope · one fixed number