Skip to content

Supabase Database Tables — Design PostgreSQL Schemas in Supabase

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Supabase Database Tables. We cover key concepts, practical examples, and best practices to help you master this topic.

Supabase database tables are PostgreSQL tables managed through the SQL Editor, Table Editor, or directly via SQL migrations, supporting all standard PostgreSQL features like constraints and indexes.

What You'll Learn

By the end of this lesson you will create tables with the Table Editor and SQL Editor, define relationships using foreign keys, add indexes for performance, and understand Supabase table conventions.

Why It Matters

Well-designed database tables are the foundation of every Supabase project. Proper schemas with correct data types, constraints, and indexes ensure data integrity and query performance.

Real-World Use

DodaZIP stores user profiles, file metadata, and processing status in Supabase tables. The processing_jobs table tracks file compression progress with foreign keys to user_profiles.

flowchart LR
    subgraph "Database Schema"
        T1[users] --> T2[profiles]
        T1 --> T3[files]
        T3 --> T4[processing_jobs]
    end
    style T1 fill:#3ecf8e,color:#fff

Creating Tables with SQL

Supabase provides full PostgreSQL SQL access.

-- Create users table
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email TEXT UNIQUE NOT NULL,
    full_name TEXT,
    avatar_url TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Create profiles table with foreign key
CREATE TABLE profiles (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    bio TEXT,
    website TEXT,
    company TEXT
);

-- Create files table
CREATE TABLE files (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    size_bytes BIGINT,
    mime_type TEXT,
    storage_path TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Add indexes
CREATE INDEX idx_files_user_id ON files(user_id);
CREATE INDEX idx_files_created_at ON files(created_at DESC);

Creating Tables with Table Editor

Supabase's Table Editor provides a graphical interface.

# table_designer.py
# Understanding table editor options

def table_editor_options():
    options = {
        "Name": "Descriptive, snake_case table name (e.g., user_profiles)",
        "Columns": "Add columns with names and data types",
        "Default Value": "Set default values (e.g., NOW() for timestamps)",
        "Primary Key": "Choose a column or composite key as primary key",
        "Foreign Keys": "Reference columns from other tables",
        "Indexes": "Add performance indexes on frequently queried columns",
        "RLS Enabled": "Enable Row Level Security for the table",
        "Realtime Enabled": "Enable realtime subscriptions for this table",
    }
    
    print("Table Editor Options:")
    for option, desc in options.items():
        print(f"  {option:20s} | {desc}")

table_editor_options()

Data Types

Choose the right PostgreSQL data types for your columns.

# data_types.py
# PostgreSQL data types for Supabase

def common_data_types():
    types = {
        "UUID": "Primary keys and unique identifiers (gen_random_uuid())",
        "TEXT": "Variable-length strings for names, descriptions, content",
        "VARCHAR(n)": "Fixed-length strings like emails or phone numbers",
        "INTEGER": "Whole numbers for counts and IDs",
        "BIGINT": "Large whole numbers for file sizes",
        "NUMERIC(p,s)": "Precise decimal numbers for prices",
        "BOOLEAN": "True/false values for flags",
        "TIMESTAMPTZ": "Timestamps with timezone for created_at/updated_at",
        "JSONB": "Flexible JSON data for dynamic fields",
        "BYTEA": "Binary data for small files or hashes",
    }
    
    print("Common PostgreSQL Data Types:")
    for dtype, desc in types.items():
        print(f"  {dtype:15s} | {desc}")

common_data_types()

Schema Design Example

Design a schema for a file processing application.

CREATE TABLE file_sources (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    bucket TEXT NOT NULL DEFAULT 'files',
    path TEXT NOT NULL,
    size_bytes BIGINT,
    content_hash TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE processing_results (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    file_id UUID REFERENCES file_sources(id) ON DELETE CASCADE,
    status TEXT CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
    result_data JSONB,
    started_at TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    error_message TEXT
);

-- Enable RLS
ALTER TABLE file_sources ENABLE ROW LEVEL SECURITY;
ALTER TABLE processing_results ENABLE ROW LEVEL SECURITY;

Common Mistakes

  1. Not using UUID primary keys: UUIDs are more secure than SERIAL because they cannot be enumerated by attackers.

  2. Forgetting to enable RLS: Tables in Supabase have RLS enabled by default in newer projects. Disabling it without understanding the implications exposes data.

  3. Missing foreign key indexes: While PostgreSQL creates indexes for primary keys, it does not automatically index foreign key columns.

  4. Using the wrong data type for timestamps: Use TIMESTAMPTZ (with timezone) instead of TIMESTAMP to handle users in different time zones correctly.

  5. No created_at/updated_at columns: Always add these columns. They are essential for debugging, sorting, and auditing.

Practice Questions

  1. What are the two ways to create tables in Supabase? The SQL Editor (raw SQL) and the Table Editor (graphical interface).

  2. Why use UUID instead of SERIAL for primary keys? UUIDs prevent enumeration attacks and are globally unique, making them safer for multi-tenant applications.

  3. What does ON DELETE CASCADE do? It automatically deletes related rows when the parent row is deleted.

  4. How do you create an index in Supabase? Use CREATE INDEX index_name ON table_name(column_name).

  5. Challenge: Design a database schema for a blog with tables for authors, posts, categories, tags, and comments with appropriate relationships and indexes.

FAQ

Can I use the Table Editor for complex schemas?

The Table Editor is useful for simple tables. Use the SQL Editor for complex schemas, migrations, and production changes.

What happens if I delete a table in Supabase?

Deleting a table removes all data permanently. There is no undo. Use migrations for schema changes.

Does Supabase support composite primary keys?

Yes. Supabase supports composite primary keys and composite foreign keys.

Can I import existing data into Supabase tables?

Yes. Use the SQL Editor to run INSERT statements, or the dashboard import tool for CSV files.

How do I rename a column in Supabase?

Use ALTER TABLE table_name RENAME COLUMN old_name TO new_name in the SQL Editor.

Mini Project

Create a complete schema for a task management application with tables for users, projects, tasks, comments, and attachments. Add appropriate primary keys, foreign keys, indexes, and enable RLS on all tables.

def task_app_schema():
    tables = [
        "users (id UUID PK, email TEXT, full_name TEXT, avatar_url TEXT)",
        "projects (id UUID PK, user_id UUID FK->users, name TEXT, description TEXT, created_at TIMESTAMPTZ)",
        "tasks (id UUID PK, project_id UUID FK->projects, title TEXT, status TEXT, priority TEXT, assignee_id UUID FK->users, due_date DATE)",
        "comments (id UUID PK, task_id UUID FK->tasks, user_id UUID FK->users, content TEXT, created_at TIMESTAMPTZ)",
        "attachments (id UUID PK, task_id UUID FK->tasks, file_path TEXT, file_name TEXT, file_size BIGINT)",
    ]
    
    print("Task Management Schema:")
    for table in tables:
        print(f"  {table}")

task_app_schema()

What's Next

Next: Row Level Security for protecting your data.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro