Database Schema Design for a SaaS MVP in 2026
You've sketched your SaaS idea on a whiteboard — users, accounts, billing, some core feature. Now you need a database schema. The temptation is to over-design: normalize everything to 5NF, plan for millions of users, add
You've sketched your SaaS idea on a whiteboard — users, accounts, billing, some core feature. Now you need a database schema. The temptation is to over-design: normalize everything to 5NF, plan for millions of users, add every index you can think of. The reality is that 80% of schema decisions made in week one get rewritten by month six as product direction shifts.
This framework isn't about academic database design principles. It's about making the minimum set of structural decisions that won't block your development velocity or require painful migrations later.
Quick Answer
Start with a single Postgres database, a users table (using UUIDs, not auto-increment IDs), an accounts/organizations table if you have multi-tenant users, and a subscriptions table that references your payment provider's IDs. Use JSONB columns for flexible attribute storage (pricing plans, user preferences, feature flags) — you can normalize them into proper tables once the data model stabilizes. Skip composite indexes until you have query EXPLAIN output to justify them.
Why This Matters in 2026
Three trends make schema design decisions particularly impactful this year:
- AI-generated code — The next generation of development tools (Cursor, Aider, Copilot) produce better code when your schema is self-documenting. Clear table and column names with correct types reduce hallucinations in AI-generated queries.
- Serverless database adoption — Your 2026 schema needs to work with Neons scale-to-zero or Planetscales branching workflow. Complex relational designs with heavy trigger usage and custom data types may not port cleanly to serverless databases. Pricing visibility — Every query you write has a measurable API cost on serverless databases. A schema that requires 5 JOINs for a simple dashboard view costs more than one that surfaces the same data in 2-3 JOINs.
Common Schema Mistakes Solo Founders Make
Mistake 1: Auto-Increment IDs Everywhere
Auto-increment integers are fine for internal joins but break in distributed databases, expose record count to users, and create migration pain when you move from Postgres to a serverless alternative. Use UUID v7 (time-sorted UUIDs that support indexing) for all primary keys. The 16-byte storage cost is negligible at startup scale and saves migration pain later.
Mistake 2: Premature Normalization
Splitting user settings into 5 tables (notification_preferences, display_preferences, privacy_settings...) before you know which settings you actually need creates pain for every feature iteration. Use JSONB columns for flexible settings, then normalize once the schema stabilizes after 3-6 months of real usage data.
Mistake 3: No Audit Columns
Every table should have created_at, updated_at, and optionally deleted_at. These columns save hours of debugging — "when did this record change?" is the most common question you can't answer if you didn't track timestamps.
Minimum Viable Schema Template
Core Tables for Any SaaS
| Table | Key Columns | Purpose |
|---|---|---|
| users | id (UUID v7), email, name, auth_provider_id, avatar_url, created_at, updated_at | Authentication and user identity |
| accounts | id (UUID v7), name, slug, billing_email, plan_tier, created_at, updated_at | Multi-tenant account/org structure |
| account_members | account_id, user_id, role (enum: admin/member/viewer), joined_at | User-to-account membership |
| subscriptions | id, account_id, provider (Stripe/LemonSqueezy), provider_subscription_id, status, current_period_start, current_period_end | Billing subscription tracking |
| feature_flags | account_id, flag_name (varchar), enabled (boolean), metadata (JSONB) | Per-account feature rollout |
``
Optional but Recommended
| Table | Key Columns | Purpose |
|---|---|---|
| audit_logs | id, actor_id, action (varchar), target_type, target_id, changes (JSONB), created_at | Track all state-changing operations for debugging and compliance |
| api_keys | id, account_id, key_hash (not plaintext), name, scopes (JSONB), expires_at, created_at | API access for integrations and programmatic usage |
| webhook_endpoints | id, account_id, url, secret, enabled_events (text[]), created_at | Webhook delivery configuration |
``
Indexing Strategy (Start Minimal)
- Always create: Primary key index (auto), unique index on
users.email, composite index onaccount_members(account_id, user_id), index onsubscriptions.account_id - Add when needed: Composite indexes for specific queries you've profiled. Use
EXPLAIN ANALYZEon slow queries before adding indexes — premature indexing adds write overhead without query benefit. Avoid: Indexes on boolean columns, indexes on JSONB paths until confirmed in EXPLAIN output, and covering indexes until you have actual query performance data.
Migration Strategy
Use additive migrations whenever possible. Adding a column or table is reversible. Dropping or renaming a column requires coordination. Rules:
- Always add columns as
nullablefirst, backfill data, then add NOT NULL constraint. - Never rename columns — add the new column, dual-write for one release cycle, then drop the old column. Use Drizzle Kit or Prisma Migrate for auto-generated migration SQL, but always review the SQL before applying in production.
Limitations of This Framework
- Not for time-series data — If your app tracks events, metrics, or logs at high volume (>100K events/day), consider a time-series database (TimescaleDB, InfluxDB) or a dedicated log service. Postgres can handle 100K rows/table but query performance degrades without careful partitioning.
- Not for full-text search-heavy apps — Postgres full-text search works for basic search, but if your app's primary feature is search across millions of documents, you need Meilisearch, Typesense, or Elasticsearch alongside Postgres.
- Not for real-time collaboration — If your app requires live multi-user editing (like Notion or Figma), the schema needs CRDT-aware data structures. This framework assumes a conventional request-response data model. UUID performance — UUID v7 is better than v4 for index performance but still slightly slower than integer IDs on inserts (~5-10% overhead tested on Postgres 17). For most indie SaaS apps, this is negligible.
The Bottom Line
Your MVP schema should be minimal, additive, and self-documenting. Use UUIDs, JSONB for flexible attributes, and additive migrations. Don't design for scale you don't have — design for developer speed and easy refactoring. A schema that takes two hours to design and gets rewritten in month three is cheaper than a schema that takes two days to design and locks you into choices that don't fit your actual product.
Related Articles
- SaaS MVP build guide — Connect schema planning to the full product launch sequence.
- Prisma vs Drizzle vs TypeORM — Choose the ORM after you know your data model. D1 vs Turso vs LibSQL — Compare database infrastructure for edge SaaS apps.
Related reading on PikVue:
- How to Choose Between Open Source and SaaS Tools in 2026: A Decision Framework f
- How to Build a SaaS MVP in 2026: A Practical Guide for Solo Founders
- Which Uptime and Status Page Tool Fits a Solo SaaS in 2026?
Originally published at pikvue.com — independent 2026 reviews and comparisons of developer tools, AI products, and SaaS platforms.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.