Database Schema
Last updated on 2026-08-30
The kit uses 18 Supabase tables organized into four domains: catalog, commerce, users, and marketing. All tables have row-level security (RLS) enabled.
Schema Overview
Catalog: products, product_variants, product_images, categories,
subcategories, collections, collection_products, product_bundles
Commerce: orders, order_items, order_notes, cart_items, wishlists,
shipping_methods, return_requests
Users: profiles, addresses
Marketing: discount_codes, reviews
Tables
products
The core product table. Every storefront query starts here.
| Column | Type | Notes |
|---|---|---|
id |
uuid (PK) | Auto-generated |
name |
text | Product name |
slug |
text (unique) | URL-friendly identifier |
description |
text | Short description |
long_description |
text | Detailed product description |
features |
text[] | Feature bullet points |
brand |
text | Brand name |
price |
numeric | Regular price |
sale_price |
numeric | Sale price (nullable) |
category_id |
uuid (FK) | References categories.id |
subcategory |
text | Subcategory name |
tags |
text[] | Searchable tags |
status |
text | active, draft, or archived |
stock |
integer | Available quantity |
sku |
text | Stock keeping unit |
specs |
jsonb | Product specifications |
rating |
numeric | Average rating (auto-calculated by trigger) |
review_count |
integer | Total reviews (auto-calculated by trigger) |
product_variants
Size and color options for each product.
| Column | Type | Notes |
|---|---|---|
id |
uuid (PK) | |
product_id |
uuid (FK) | References products.id |
name |
text | Display name (e.g., "Small", "Red") |
type |
text | size or color |
value |
text | The variant value |
stock |
integer | Variant-specific stock |
price_adjustment |
numeric | Price delta from base |
product_images
Multiple images per product with ordering.
| Column | Type | Notes |
|---|---|---|
id |
uuid (PK) | |
product_id |
uuid (FK) | References products.id |
url |
text | Image URL (Supabase Storage or external) |
alt |
text | Alt text for accessibility |
position |
integer | Display order |
is_primary |
boolean | Primary image flag |
orders
Created by the Stripe webhook after successful payment.
| Column | Type | Notes |
|---|---|---|
id |
uuid (PK) | |
order_number |
text (unique) | Human-readable order number |
user_id |
uuid (FK) | References auth.users (nullable for guests) |
email |
text | Customer email |
status |
text | pending, processing, shipped, delivered, cancelled |
payment_status |
text | pending, paid, refunded |
stripe_checkout_session_id |
text | Stripe session ID |
stripe_payment_intent_id |
text | Stripe payment intent |
subtotal |
numeric | |
shipping |
numeric | |
tax |
numeric | |
discount |
numeric | |
total |
numeric | |
shipping_address |
jsonb | Full shipping address |
billing_address |
jsonb | Full billing address |
shipping_method |
text | standard, express, or overnight |
tracking_number |
text | Carrier tracking number |
discount_code |
text | Applied discount code |
cart_items
Server-side cart for authenticated users.
| Column | Type | Notes |
|---|---|---|
id |
uuid (PK) | |
user_id |
uuid (FK) | References auth.users |
product_id |
uuid (FK) | References products.id |
quantity |
integer | |
variant_selections |
jsonb | Array of {type, value} |
profiles
Auto-created on user signup via database trigger.
| Column | Type | Notes |
|---|---|---|
id |
uuid (PK, FK) | References auth.users.id |
full_name |
text | |
avatar_url |
text | |
phone |
text | |
role |
text | customer or admin |
reviews
Customer product reviews with moderation.
| Column | Type | Notes |
|---|---|---|
id |
uuid (PK) | |
product_id |
uuid (FK) | References products.id |
user_id |
uuid (FK) | References auth.users |
author_name |
text | Display name |
rating |
integer | 1-5 stars |
title |
text | Review title |
body |
text | Review body |
verified |
boolean | Verified purchase |
status |
text | pending, approved, flagged, rejected |
admin_response |
text | Admin reply |
helpful_count |
integer | Upvote count |
Row-Level Security
RLS is enabled on every table. The policies follow these principles:
| Table | Public Read | Owner Read | Owner Write | Admin Read | Admin Write |
|---|---|---|---|---|---|
| products | Active only | - | - | All | Yes |
| cart_items | - | Own rows | Own rows | - | - |
| wishlists | - | Own rows | Own rows | - | - |
| orders | - | Own rows | - | All | Yes |
| reviews | Approved only | - | Own (insert) | All | Yes |
| profiles | - | Own row | Own row | All | - |
| addresses | - | Own rows | Own rows | - | - |
| discount_codes | Active only | - | - | All | Yes |
Admin Detection
Admin access is determined by app_metadata.role:
CREATE OR REPLACE FUNCTION is_admin()
RETURNS boolean AS $$
SELECT coalesce(
auth.jwt() -> 'app_metadata' ->> 'role' = 'admin',
false
);
$$ LANGUAGE sql SECURITY DEFINER;
Triggers
Profile Auto-Creation
When a new user signs up, a trigger automatically creates their profile:
CREATE OR REPLACE FUNCTION handle_new_user()
RETURNS trigger AS $$
BEGIN
INSERT INTO public.profiles (id, full_name, avatar_url, role)
VALUES (
new.id,
coalesce(new.raw_user_meta_data ->> 'full_name', ''),
coalesce(new.raw_user_meta_data ->> 'avatar_url', ''),
'customer'
);
RETURN new;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Review Aggregation
When reviews are inserted, updated, or deleted, a trigger recalculates the product's rating and review count:
-- Automatically updates products.rating and products.review_count
-- Only counts reviews with status = 'approved'
Seed Data
The supabase/seed.sql file includes:
| Entity | Count | Notes |
|---|---|---|
| Categories | 4 | Clothing, Footwear, Accessories, Home |
| Products | 40 | With variants, images, and specs |
| Product variants | ~200 | Size and color options |
| Product images | ~160 | 4 images per product |
| Collections | 4 | Summer, trending, eco, premium |
| Reviews | 12 | With ratings and responses |
| Orders | 5 | Various statuses |
| Customers | 8 | With profiles and addresses |
| Admin user | 1 | admin@example.com / password123 |
| Discount codes | 3 | Percentage and fixed-amount |
| Shipping methods | 4 | Standard, express, overnight, free |
Regenerating Types
After modifying the schema, regenerate TypeScript types:
npx supabase gen types typescript --project-id your-project-id > types/database.ts
This updates the typed Supabase client used throughout the application.