Skip to content
Projects
4 min read

Shopify Product Label App

A private, single-store Shopify app that shows rule-driven marketing labels on product cards in collection and search pages, built first for the Crocs/mikesport store and then cloned and re-skinned as an isolated app for MikeSport Lebanon.

Role
Sole engineer
Year
2026
Status
live
shopifyremixe-commercefull-stackprismatypescript

// Architecture flow

Overview

This is a private, single-store Shopify app that displays marketing labels and banners on product cards in collection (PLP) and search pages, on both desktop and mobile. Which label shows on which product is controlled by a tag/metafield rule engine configured in a Polaris admin UI, not hardcoded per product. The app was built for the Crocs / mikesport store, then cloned into a fully isolated second codebase and re-skinned for the MikeSport Lebanon store, keeping the same architecture and logic but a different label design and separate deployment.

Problem

The merchant wanted promotional badges (like "End of Season SALE" or "Buy 3 Get 1 Free") to appear automatically on the right products across collection and search grids, based on product tags and metafields, without a developer manually editing theme templates or product data for every campaign. It also had to be safe: no risk of corrupting product data, and fully reversible if something went wrong. When a second store (MikeSport Lebanon) needed the same capability with a different look, the requirement was strict isolation: a separate app, database, and deployment that could never touch the original Crocs store.

Approach

The app reads product tags and metafields through the Shopify Admin API (read_products only, no write scopes) and evaluates them against label rules (contains / not_contains / equals / not_equals, combined with AND/OR) defined by the merchant in the app's own Polaris admin. Matching results are pre-computed into a product handle to label ids index stored in the app's own database, refreshed by product webhooks (create/update/delete) and a scheduled cron recompute (also used to flip time-scheduled labels on/off). A Shopify Theme App Extension on the storefront fetches that index through the app proxy and injects the label banners into the DOM as app-owned nodes, so the underlying theme and product data are never modified. Overlapping label matches are resolved with a priority order and a max-per-card cap, and labels can be scoped to specific placements (banner-below, corner-badge, pill-above, image-bottom) with per-side desktop/mobile styling and a live preview in the admin editor.

For MikeSport Lebanon, the Crocs codebase was cloned into a brand-new, isolated repository rather than adding multi-tenancy to the original app. Every store-specific identifier was renamed (CSS prefix, window global, app-proxy subpath, extension UUID, API keys, database) and the label design was re-skinned to a different visual style while the rule engine, data model, and Theme App Extension mechanics stayed the same.

Architecture

  • Remix (v2) app on @shopify/shopify-app-remix v4, embedded in the Shopify admin via App Bridge React, UI built with Polaris v12.
  • Rule engine (app/lib/rule-engine.server.ts) is pure and unit-tested; evaluates label conditions against product tags/metafields.
  • Recompute pipeline (app/lib/recompute.server.ts) pulls products from the Admin API and rebuilds the label-to-product index in Prisma (SQLite in dev/VPS, swappable to Postgres).
  • Product webhooks (products/create|update|delete) plus a token-protected cron/recompute endpoint keep the index current and flip scheduled labels on/off.
  • App proxy route exposes the index to the storefront without exposing the Admin API.
  • Theme App Extension (extensions/product-labels, a Liquid app block plus CSS/JS assets) fetches the index and injects labels only into collection/search product cards, explicitly excluding recommendation carousels/sliders via configurable selectors.
  • MikeSport Lebanon runs as a second, completely separate instance: its own repo, Partner app/client id, subdomain, port, pm2 process, nginx block, TLS cert, and CSS/JS namespacing, coexisting on the same VPS without sharing any state with the Crocs app.

Tech stack

  • Remix v2, React 18, TypeScript
  • Shopify: @shopify/shopify-app-remix, App Bridge React, Polaris v12, Theme App Extension, App Proxy, webhooks
  • Prisma v6 + SQLite (Postgres-compatible) for the label index and session storage
  • Node 20/22, pm2, nginx, certbot (Let's Encrypt), Hostinger VPS
  • node --test for rule-engine unit tests

Engineering highlights

  • Read-only and fully reversible: the app only holds read_products scope, never writes to Shopify data, and all storefront changes are app-owned DOM nodes injected by the Theme App Extension; disabling the app embed removes every trace instantly.
  • Rule engine is a pure, independently tested module (AND/OR tag/metafield matching, priority plus max-per-card cap, scheduling), decoupled from the Shopify API and storefront rendering.
  • Pre-computed index pattern: instead of evaluating rules on every storefront request, a background recompute (webhooks plus cron) keeps a small per-shop index that the storefront reads cheaply through the app proxy.
  • Clean re-skin/isolation for a second store: cloning to a new repo, renaming every store-specific identifier, and standing up a fully separate VPS tenant (own port, pm2 process, nginx block, TLS cert) so the two stores share zero infrastructure or data.
  • Explicit exclusion logic to keep labels out of recommendation carousels/sliders, configurable per theme via CSS selectors in the app embed settings.

Outcome

The Crocs/mikesport version was built and shipped, including admin CRUD for labels, live preview, per-label page targeting, and storefront alignment fixes for banner-below labels across grid rows. The MikeSport Lebanon clone was then built and deployed as an isolated repo, Partner app, and VPS tenant, released the Theme App Extension via shopify app deploy, and passed its own test suite and production build before install on the MikeSport store.

Want to dig deeper?

Ask my AI agent anything about how this was built, what tradeoffs I made, or how it could fit your team.

Ask my AI →