At a glance
The count of SKUs that disagree between your Supabase products table (the source of truth your app reads from) and the live ecommerce catalogue (Shopify, BigCommerce or Adobe Commerce) the storefront actually sells from. When the two drift apart, customers see prices, stock levels or product states that no longer match your database: a SKU marked active in Postgres but hidden on the storefront, a price that updated in one system but not the other, or stock that says 12 in Supabase and 0 in Shopify. Every drifting SKU is a potential mispriced order, an oversell, or a product nobody can buy. The card compares row-by-row and surfaces the count of disagreements so a platform team can catch a broken sync before finance does.
| What it tracks | The number of SKUs where the Supabase products table and the connected ecommerce catalogue disagree on a watched field (price, inventory quantity, active/published state, or title). A drift is a single SKU with at least one mismatched field. |
| Data source | Supabase Products Table vs Ecom Catalog for the selected period. Two reads are joined on SKU: the left side is the project’s products table over the Supabase Postgres connection (or PostgREST); the right side is the catalogue pulled from the live commerce connector (Shopify Admin API, BigCommerce Catalog API or Adobe Commerce REST). |
| Time window | RT/24h. The headline drift count is recomputed in near real time as rows change; the trend strip shows drift over the last 24 hours so you can see whether a sync break is new or chronic. |
| Alert trigger | >0 SKUs drifting. This card is deliberately strict: a healthy bidirectional sync should hold zero drift, so any non-zero count feeds the Nerve Centre incident feed for review. |
| Why it matters | This is the cross-channel revenue-at-risk join. A drift between your database and your storefront is not a graph that wobbles, it is money: a price that never propagated, an item still on sale that you have discontinued, or stock you do not actually hold. The card turns a silent integration failure into a visible, costed number. |
| Reading the value | 0 is the only resting state. A small steady count (1 to 5) usually means a single stuck product or a partial sync run. A sudden jump into the tens or hundreds means the sync job has stopped, a credential has expired, or a bulk import wrote to one system only. |
| Roles | platform/SRE, engineering, owner, operations |
Calculation
The card performs a keyed set comparison, not a metric aggregation. The logic, grounded in the data sources above:- Orphans (a SKU present in one system but absent from the other) are reported as a separate sub-count, not as a drift, because the cause is different (a create or delete that did not propagate, rather than a field that did not update).
- In-flight writes: a SKU that changed within the last refresh interval is given one grace cycle before it is counted, so a normal sync lag of a few seconds does not register as drift.
Worked example
A platform team runs a headless storefront: Shopify is the storefront and checkout, but the app reads product data from a Supabaseproducts table that is kept in sync by a nightly job plus a webhook for live edits. Snapshot taken on 14 Apr 26 at 09:20 BST.
The card reads 7 SKUs drifting, up from 0 the previous morning. The drilldown:
| SKU | Field | Supabase value | Shopify value | Likely cause |
|---|---|---|---|---|
| GLTC-CANDLE-AMBER | price | £18.00 | £24.00 | Price rise applied in Shopify only |
| GLTC-CANDLE-CEDAR | price | £18.00 | £24.00 | Same batch price rise |
| GLTC-CANDLE-FIG | price | £18.00 | £24.00 | Same batch price rise |
| GLTC-DIFF-100ML | inventory_qty | 12 | 0 | Oversell risk: app shows in stock |
| GLTC-DIFF-200ML | inventory_qty | 6 | 0 | Oversell risk |
| GLTC-GIFTSET-A | is_active | active | archived | Discontinued in Shopify, still live in app |
| GLTC-GIFTSET-B | is_active | active | archived | Same |
- Drift is money, not noise. Unlike a latency graph, every unit on this card maps to a concrete SKU that can be mispriced, oversold, or unbuyable. Treat a non-zero count as a financial incident, not a monitoring curiosity.
- The field column is the diagnosis. A wall of
pricemismatches points at a pricing sync; a wall ofinventory_qtypoints at stock sync; mixed fields point at a stalled job that touches everything. Read the breakdown, not just the count. - Direction matters for risk. Supabase-high / ecom-low on stock is an oversell risk; ecom-high / Supabase-low is a missed-sale risk. The drilldown shows which way each SKU leans so you can triage by blast radius.
Sibling cards
| Card | Why pair it with Products Table vs Ecom Catalog | What the combination tells you |
|---|---|---|
| Supabase QPS Spike vs Ecom Order Rate | The other cross-channel revenue-at-risk join. | Drift plus a QPS spike with no matching order spike can mean a sync or import job is hammering the database, which is also what broke the catalogue parity. |
| Slow PostgREST Queries During Checkout Window | The query-level view during the moment that matters. | If the sync read is itself one of the slow queries, parity breaks because the reconcile cannot keep up. |
| Database Query Error Rate % | The failure rate of the queries that drive the sync. | A rising error rate on the sync’s writes is the most common silent cause of new drift. |
| PostgREST 5xx Error Rate % | If the sync runs over PostgREST, 5xx on that layer stops propagation. | 5xx spike plus rising drift equals “the API the sync uses is failing”. |
| Supabase Health Score | The executive composite. | Drift on a healthy database isolates the problem to the integration, not the data store. |
| Last Backup Age (hours) | Your recovery position before you reconcile. | Before a bulk reconcile that mass-updates rows, confirm a recent backup exists. |
Reconciling against the source
This card is itself a reconciliation, so “reconciling against the source” means checking each side independently with its own native tooling. The Supabase side (your source of truth):Run the comparison query directly in the Supabase SQL Editor (Database → SQL Editor) or viaThe ecommerce side (what the storefront sells):psqlover the project connection string:SELECT sku, price, inventory_qty, is_active FROM products ORDER BY sku;Cross-check counts with the Table Editor row count and withpg_stat_user_tables.n_live_tupfor theproductstable to confirm you are reading the full set, not a filtered view affected by row-level security.
- Shopify: the Admin API
productsandvariantsendpoints, or a CSV export from Products → Export. The variant SKU is the join key, not the product ID. - BigCommerce: the Catalog API
/v3/catalog/products, or Products → Export in the control panel. - Adobe Commerce: the REST
GET /V1/productsendpoint, or a product export from Catalog → Products.
| Reason | Direction | Why |
|---|---|---|
| Refresh grace cycle | Vortex IQ count lower | A SKU edited within the last cycle is held back one refresh, so a manual check during a sync run can show a transient mismatch we have not yet counted. |
| Currency rounding | Variable | We compare price to 2 dp in the catalogue currency; a manual check against a raw decimal column may flag rounding differences we treat as equal. |
| Row-level security | Vortex IQ count differs | If the connection role is subject to RLS, the Supabase set may exclude rows you can see as the project owner in the SQL Editor. Use the service role for parity, or scope your manual query to match. |
| Orphans counted separately | Vortex IQ count lower | A SKU present in only one system is reported as an orphan, not a drift, so the headline drift number excludes it. |
| Watched-field scope | Vortex IQ count lower | We compare a fixed field set (price, inventory, state, title). A field outside that set (a custom metafield) will not register as drift here even if it differs. |
Known limitations / FAQs
Myproducts table is not called products, or the SKU column has a different name. Does the card still work?
Yes, but it needs to be told where to look. The card reads the table and column mapping from the connector configuration (Settings → Supabase → Catalogue mapping). Point it at your table and the column that holds the SKU, plus which columns map to price, inventory and state. Until that mapping is set, the card cannot join the two sides and will read as not configured rather than zero.
The count is non-zero but every SKU listed looks correct when I check by hand.
Three usual causes: (1) a price stored as a string vs a numeric in one system, which compares unequal even when the value matches, fix by normalising the column type; (2) trailing whitespace or case differences in the title field; (3) an RLS-filtered read so the two sides are not seeing the same rows. Check the field column in the drilldown: if every drift is on the same field, the cause is almost always a type or formatting mismatch, not real data divergence.
What is the difference between a drift and an orphan?
A drift is a SKU present in both systems that disagrees on a watched field, that is a sync that ran but wrote the wrong value or only updated one side. An orphan is a SKU present in only one system, that is a create or delete that never propagated. They feed the same card but are counted separately because the remediation differs: a drift needs a field re-sync, an orphan needs a create/delete to be replayed.
Does a non-zero count automatically pause anything, like ad spend or checkout?
No. The card is a detection signal, not a control. It raises a Nerve Centre incident at >0 so a human can decide. Whether to pause a sync, hold a price-sensitive campaign, or gate add-to-basket is an operational call, and the right call depends on which field is drifting and in which direction.
How fast will the count clear after I fix the sync?
On the next refresh cycle once both sides agree, subject to the one-cycle grace period for rows that changed during the fix. If you run a bulk reconcile, expect the count to tick down as rows settle rather than snap to zero instantly, because each corrected SKU passes through the grace window once.
The Free tier of Supabase, does this card work there?
The comparison works on any tier because it only needs read access to your products table and the commerce connector. There is no backup or PITR dependency here (unlike Last Backup Age (hours)). The only tier-sensitive concern is that heavy reconcile reads on a small Free-tier instance can compete with app traffic; schedule large reconciles off-peak.
Can the card tell me which direction is authoritative?
It does not assume one. It reports the value on each side and the field that disagrees, leaving authority to you. Most teams treat the ecommerce platform as authoritative for price and state (because that is what the customer transacts against) and the database as authoritative for richer attributes, but the card does not enforce a direction, it only surfaces the disagreement.