Data Model

public schema tables ship with RLS turned on. Six ordered migrations under supabase/ build enums, tables, and policies: run them in Supabase SQL before writing app code.

Enums

workspace_role      : OWNER | ADMIN | MEMBER | VIEWER
product_status      : DRAFT | ACTIVE | ARCHIVED
subscription_status : active | trialing | past_due | canceled | incomplete

Core tables

workspaces

The top-level tenant container. All workspace-scoped data references this table.

ColumnTypeNotes
iduuid PK
nametextDisplay name
slugtext uniqueURL-safe identifier
setup_completebooleanOnboarding flag (default: false)
require_2fabooleanEnforce 2FA for all members (default: false)
created_byuuid -> auth.users
created_attimestamptz

profiles

Public user metadata synced from auth.users via a database trigger.

ColumnTypeNotes
iduuid PK -> auth.users
emailtext
full_nametext
avatar_urltext
updated_attimestamptz

memberships

Joins users to workspaces with a role.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces
user_iduuid -> auth.users
roleworkspace_roleOWNER, ADMIN, MEMBER, or VIEWER
created_attimestamptz

Unique constraint on (workspace_id, user_id).

invitations

Pending email invitations.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces
emailtextInvitee's email
roleworkspace_roleRole to grant on acceptance
tokentext uniqueRandom 32-byte hex token
invited_byuuid -> auth.users
expires_attimestamptzDefault: now + 7 days
accepted_attimestamptzNULL until accepted
created_attimestamptz

products

The primary domain entity (replace with your own model). See Adding Your Own Model.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces
nametext
descriptiontextNullable
statusproduct_statusDefault: DRAFT
custom_fieldsjsonbCustom field values (default: )
created_byuuid -> auth.users
created_attimestamptz
updated_attimestamptzAuto-updated via trigger
deleted_attimestamptzNULL = active, set = soft-deleted

subscriptions

One row per workspace, managed by the Stripe webhook handler.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces (unique)
stripe_customer_idtext
stripe_subscription_idtext
plantextstarter, pro, enterprise
statussubscription_status
current_period_endtimestamptz
cancel_at_period_endboolean
created_attimestamptz

audit_logs

Append-only event log. No updates or deletes.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces
actor_user_iduuid -> auth.users
actiontexte.g. product.created
resource_typetexte.g. product
resource_idtextID of the affected row
metadatajsonbExtra data (old/new values, etc.)
created_attimestamptz

Collaboration tables

tags

Workspace-scoped colored labels.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces
nametextUnique per workspace
colortextHex color (default: #6B7280)
created_attimestamptz

record_tags

Many-to-many: assigns tags to any record type.

ColumnTypeNotes
record_iduuid
tag_iduuid -> tags
record_typetexte.g. product, task
created_attimestamptz

Primary key: (record_id, tag_id, record_type).

comments

Threaded comments on any record.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces
record_iduuid
record_typetexte.g. product
author_iduuid -> auth.users
bodytext1-5000 characters
mentionsuuid[]Mentioned user IDs
parent_iduuid -> commentsFor threaded replies
created_attimestamptz
updated_attimestamptzAuto-updated via trigger

attachments

File attachments on any record.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces
record_iduuid
record_typetextDefault: product
file_nametext
file_sizebigint
mime_typetext
storage_pathtextSupabase Storage path
uploaded_byuuid -> auth.users
created_attimestamptz

Developer platform tables

api_keys

Workspace API keys for programmatic access.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces
nametext
key_hashtextSHA-256 hash of the full key
key_prefixtexte.g. sk_live_
last_fourtextLast 4 characters for display
scopestext[]Default: {read}
last_used_attimestamptz
expires_attimestamptz
created_byuuid -> auth.users
created_attimestamptz
revoked_attimestamptz

webhooks

Outgoing webhook registrations.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces
urltextTarget URL
eventstext[]Subscribed event types
secrettextSigning secret
is_activeboolean
created_byuuid -> auth.users
created_attimestamptz

webhook_deliveries

Delivery tracking for outgoing webhooks.

ColumnTypeNotes
iduuid PK
webhook_iduuid -> webhooks
event_typetext
payloadjsonb
response_statusintegerHTTP status code
response_bodytext
attemptsintegerRetry count
delivered_attimestamptz
created_attimestamptz

webhook_events

Incoming webhook events from external services.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces
sourcetextService name
event_typetext
payloadjsonb
processed_attimestamptz
created_attimestamptz

User feature tables

bookmarks

Per-user bookmarks within a workspace.

ColumnTypeNotes
iduuid PK
user_iduuid -> auth.users
workspace_iduuid -> workspaces
resource_typetext
resource_iduuid
resource_nametextDisplay name
sort_orderinteger
created_attimestamptz

Unique constraint on (user_id, workspace_id, resource_type, resource_id).

saved_views

Saved data table configurations.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces
created_byuuid -> auth.users
nametext
resource_typetextDefault: product
filtersjsonb
sortjsonb
columnsjsonb
is_sharedbooleanVisible to all members
created_attimestamptz

custom_field_definitions

Schema for dynamic custom fields.

ColumnTypeNotes
iduuid PK
workspace_iduuid -> workspaces
resource_typetextDefault: product
nametext
field_typetexttext, number, date, select, multi_select, url, boolean
optionsjsonbFor select/multi_select types
is_requiredboolean
sort_orderinteger
created_attimestamptz

notification_preferences

Per-user notification settings.

login_history

Sign-in attempt records with IP and device info.

scheduled_actions

Scheduled tasks with execution tracking.

workspace_ip_allowlist

IP CIDR ranges for workspace access restriction (Enterprise).

Security tables

All security-related data is stored in dedicated tables. See Authentication & Security for details.

Soft delete pattern

Products use soft delete via a deleted_at column. When a product is "deleted," the timestamp is set instead of removing the row.

  • Active products: WHERE deleted_at IS NULL
  • Trashed products: WHERE deleted_at IS NOT NULL
  • Permanent delete: Removes the row entirely (OWNER/ADMIN only)

Partial indexes optimize both query paths:

  • products_not_deleted_idx -- covers active product queries
  • products_deleted_idx -- covers trash queries

To add soft delete to your own tables, follow the pattern in supabase/003_soft_delete_and_dashboard.sql.

Database functions

daily_audit_counts(p_workspace_id, p_days)

Returns (day DATE, count BIGINT) for the dashboard activity chart. Days with no activity return 0.

SELECT * FROM daily_audit_counts('workspace-uuid', 30);

is_workspace_member(workspace_id)

RLS helper. Returns true if the current user is a member of the specified workspace.

workspace_role(workspace_id)

RLS helper. Returns the current user's role in the specified workspace.

Key relations

auth.users --< memberships >-- workspaces
                                 ├── products
                                 ├── invitations
                                 ├── subscriptions
                                 ├── audit_logs
                                 ├── tags / record_tags
                                 ├── comments
                                 ├── attachments
                                 ├── api_keys
                                 ├── webhooks / webhook_deliveries
                                 ├── webhook_events
                                 ├── bookmarks
                                 ├── saved_views
                                 ├── custom_field_definitions
                                 ├── notification_preferences
                                 ├── scheduled_actions
                                 └── workspace_ip_allowlist

auth.users --< login_history

Workspace deletion cascades to all child tables except profiles (user-owned).