Data Model
All data lives in Supabase Postgres. Access is server-side only via the admin client
(lib/server/supabase-admin.ts, service-role key). Storage uses three buckets
(input / output / merchant images).
Core tables
| Table | What it holds |
|---|---|
stores | One row per installed store: shop_domain, plan/billing state, trial dates, generation provider/model, custom-plan fields, usage counters |
platform_connections | Shopify access tokens (encrypted) + scopes per store |
catalog_products | Synced Shopify products |
catalog_product_images | Product images (incl. primary reference image) |
product_tryon_settings | Per-product widget enable/disable + overrides |
store_widget_config | Widget appearance/config per store |
storefront_theme_settings | Theme/embed detection state |
Try-on flow tables
| Table | What it holds |
|---|---|
tryon_sessions | A shopper try-on session (status, shopper identity, product) |
tryon_input_images | Uploaded person images (storage bucket + path, expiry) |
tryon_provider_jobs | The provider task (KIE/fal task id, status, payloads) |
tryon_outputs | Generated result images (storage bucket + path) |
analytics_events | Funnel events (widget click, upload, generation, result view, add-to-cart, purchase) |
attributed_orders | Orders linked to try-on sessions (conversion attribution) |
shopper_rate_limit_usage | Per-shopper rate-limit counters + email-gate bonus state |
Growth / ops tables
| Table | What it holds |
|---|---|
captured_emails | Emails captured via the widget email gate and landing demo (source=landing_demo) and contact inquiries (source=contact_inquiry). Conflict key (store_id, email) |
discount_codes | Shopify discount codes created for post-try-on incentives |
audit_events | IP/abuse audit + rate-limit window enforcement |
webhook_events | Received Shopify webhooks (idempotency/log) |
sync_jobs | Product sync job tracking |
app_logs | Structured app logs (queryable from the admin Logs tab) |
app_config | Misc app-level config (e.g. log level) |
billing_events | Append-only billing audit ledger — plan changes (from→to), per-period try-on usage snapshots, overage charges, subscription status transitions, and failures (success/error_message). Written by recordBillingEvent() from every billing path; see Billing Pipeline |
Notable patterns
captured_emailsis reused as the store for demo leads and contact inquiries (no dedicated tables) — distinguished by thesourcecolumn andmetadataJSON. The demo store id (teststoretryon) is used as thestore_idfor these “system” records.stores.custom_plan+custom_price/custom_try_on_limit/custom_overage_ratedrive custom plans;getPlanLimit()/getOverageRate()inlib/billing/plans.tsread them.reconcileStoreBillingis the authority: it keeps these set while the active sub is the custom one and clears them (CLEARED_CUSTOM_PLAN_FIELDS) the moment the store lands on a standard tier or trial.- Usage/period columns on
stores:try_ons_used+overage_pendingare the live counters (atomic RPCincrement_try_ons);current_period_starts_at/current_period_ends_attrack the active 30-day Shopify cycle. The dailybilling-overagecron resets the counters whencurrent_period_ends_atadvances (Shopify fires no webhook on renewal). - Trial dates are written once on first install in
upsertStore(only whentrial_starts_at IS NULL).