Operations & platform
Dynamic Product Badges
Indicative price, not an offer: EUR 100-200/year or EUR 250 one-off
Generated from docs/plan/paid/dynamic-product-badges/PLAN.md in the project's planning notes, which are not part of the public repository. Nothing described below is implemented — it is the written plan for it.
An idea, not a commitment. It has a written plan but no place in the build order and no date. What is committed.
Static product images don’t convey promotions or inventory status. This feature adds dynamic badges (New, Sale, Low Stock, Bestseller) with rule-based activation, displayed as image overlays or in product cards.
The problem
My product cards are static. I want to show ‘New’, ‘On Sale’, ‘Last 3 left’ badges automatically based on product data, without editing images manually.
What it does
- Badge types: predefined (New, Sale, Low Stock, Bestseller, Popular) and custom text badges
- Badge rules: trigger badge based on product state (sale_price < regular_price, stock < 10, created_at < 30 days ago, sold > 100 in last 30 days)
- Visual config: badge color, position (top-left, top-right, center), text/label, optional icon
- Admin UI: toggle each badge type on/off, configure rules and visuals
- Storefront: badges render as image overlays on product cards and detail pages
- API: badge list for product (returns active badges for that product)
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Animated badges (GIF/video overlays) — static images only
- Customer-visible badge customization — admin controls visuals; customer sees result
- Badge analytics (which badges drive clicks) — display only, no interaction tracking
Data model
New entity: Badge {id, name, type, enabled, rules (JSON: field + operator + value), visual_config (color, position, label), created_at}. Example: {type: ‘sale’, rules: {sale_price: ’<’, regular_price}, label: ‘Sale’, color: ‘red’, position: ‘top-right’}.
API
- GET /api/products/:id/badges — return active badges for product (public)
- POST /api/badges — create badge (admin only)
- PATCH /api/badges/:id — update badge config (admin only)
- DELETE /api/badges/:id — delete badge (admin only)
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Badge manager: list of badge types (New, Sale, Low Stock, Bestseller). Toggle on/off. Edit: configure rules (if sale_price < regular_price, show Sale badge), visual (color: red, position: top-left, label: ‘Sale’), preview badge on sample product card.
The seam — why this is paid
Core owns the badge model and storefront rendering logic. Paid layer owns advanced analytics and targeting (show badge only for specific customer segments, A/B test badge text).
Paid marketing feature. Core provides basic product info; dynamic badges with rule-based automation is a paid marketing tool.
Dependencies
- Product entity with sale_price, created_at, stock fields (already shipped)
- Sales/order history for bestseller calculation
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- Badge ‘Sale’ is created with rule: sale_price < regular_price
- Product with regular_price €50, sale_price €40 gets the Sale badge automatically
- Badge is rendered on product card as image overlay (position: top-right, color: red, text: ‘Sale’)
- Badge ‘Low Stock’ is created with rule: stock < 10
- Product with stock=5 gets Low Stock badge; stock=10 does not
- Badge is disabled; all products stop showing that badge (no cache invalidation needed; check on render)
Risks
Badge rules must be evaluated efficiently; querying ‘sold > 100 in last 30 days’ on every product list query could be slow (index on sale timestamps or pre-compute bestseller flag). Rule evaluation can be wrong if product data is stale. Badge clutter (too many badges) reduces impact; restrict to 2–3 simultaneous badges per product.
Commercial context
| Suggested price | EUR 100-200/year or EUR 250 one-off |
| Rival anchor | Mirasvit Advanced Product Labels |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and CONTRIBUTING.md for how to work on this repository.