> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hiveku.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Database Tables

> Design your database schema with the table editor or AI

Your database is where user accounts, blog posts, form submissions, and everything else your app remembers get stored. Here's how to design tables in Hiveku.

<Info>
  You need a provisioned database first. If you haven't done that yet, see [Set Up the Database](/how-tos/setup-database).
</Info>

<CardGroup cols={3}>
  <Card title="Easiest" icon="wand-magic-sparkles">
    Describe it to the AI
  </Card>

  <Card title="Visual" icon="table">
    Use the Create Table modal
  </Card>

  <Card title="For developers" icon="code">
    Write SQL directly
  </Card>
</CardGroup>

All three paths live at:

```
Project > Database > Tables tab
```

## Option 1: Ask the AI (Easiest)

<Steps>
  <Step title="Open the AI chat">
    Click the **AI** tab in your project.
  </Step>

  <Step title="Describe the table">
    Be concrete about column names, types, and relationships:

    ```
    Create a posts table with:
    - title (text, required)
    - body (text)
    - author_id (uuid, foreign key to auth.users)
    - created_at (timestamptz, default now())
    ```
  </Step>

  <Step title="Review the plan">
    The AI shows the SQL it will run and the RLS policy it suggests. Approve or ask for changes — *"Make title unique"* or *"Add a slug column"*.
  </Step>

  <Step title="Apply">
    The AI runs the migration. Your table is live in seconds.
  </Step>
</Steps>

## Option 2: Create Table Modal (Visual)

<Steps>
  <Step title="Open Tables">
    Go to **Database > Tables** and click **+ Create Table**.
  </Step>

  <Step title="Name the table">
    Use lowercase snake\_case — `blog_posts`, not `BlogPosts` or `blog posts`.
  </Step>

  <Step title="Add columns">
    For each column pick a type:

    | Type          | Use for                                      |
    | ------------- | -------------------------------------------- |
    | `uuid`        | IDs, foreign keys to `auth.users`            |
    | `text`        | Any length of string — prefer over `varchar` |
    | `varchar(n)`  | Fixed-length strings (rare)                  |
    | `integer`     | Whole numbers up to \~2B                     |
    | `bigint`      | Whole numbers beyond 2B                      |
    | `numeric`     | Money, precise decimals                      |
    | `boolean`     | True/false flags                             |
    | `timestamptz` | Any date/time (always include timezone)      |
    | `date`        | Calendar dates with no time                  |
    | `jsonb`       | Flexible/nested data, settings, metadata     |
  </Step>

  <Step title="Set constraints">
    For each column, toggle:

    * **Primary key** (exactly one per table)
    * **Nullable** (can the value be empty?)
    * **Unique** (no duplicates)
    * **Default** value (e.g., `now()` for timestamps, `gen_random_uuid()` for UUIDs)
  </Step>

  <Step title="Create">
    Click **Create**. Hiveku runs the DDL and the table appears in your list.
  </Step>
</Steps>

<Tip>
  Hiveku auto-adds three columns to every new table so you don't have to: `id uuid PRIMARY KEY DEFAULT gen_random_uuid()`, `created_at timestamptz DEFAULT now()`, and `updated_at timestamptz DEFAULT now()`. You can remove them in the modal if you don't want them.
</Tip>

## Option 3: SQL Editor (Developers)

For complex schemas, constraints, indexes, or extensions, use raw SQL.

<Steps>
  <Step title="Open the SQL editor">
    From the Database panel, click the **SQL** tab.
  </Step>

  <Step title="Write DDL">
    ```sql theme={null}
    create table public.posts (
      id uuid primary key default gen_random_uuid(),
      title text not null,
      body text,
      author_id uuid references auth.users(id) on delete cascade,
      published boolean default false,
      metadata jsonb default '{}'::jsonb,
      created_at timestamptz default now(),
      updated_at timestamptz default now()
    );

    create index posts_author_idx on public.posts(author_id);
    ```
  </Step>

  <Step title="Run">
    Click **Run** (or press Cmd/Ctrl+Enter). Errors show inline with line numbers.
  </Step>
</Steps>

## Naming and Schema Conventions

* Table names: lowercase snake\_case, plural — `blog_posts`, `orders`, `invoices`
* Column names: lowercase snake\_case, singular — `first_name`, `is_active`, `total_cents`
* Foreign keys: reference column name ends in `_id` — `user_id`, `post_id`
* Always use `timestamptz` (never bare `timestamp`) to avoid timezone bugs
* Store money as `integer` or `bigint` cents, or `numeric(12, 2)` — never `float`

## Common Column Patterns

```sql theme={null}
-- Foreign key to the authenticated user
user_id uuid references auth.users(id) on delete cascade

-- Flexible settings or metadata
settings jsonb default '{}'::jsonb

-- Soft delete
deleted_at timestamptz

-- Slug for URLs
slug text unique not null

-- Enum-like field (pick one of a set)
status text check (status in ('draft', 'published', 'archived'))
```

## Editing an Existing Table

Click any table row in the list to open its schema editor. You can:

* Add new columns (safe for existing data)
* Rename columns (safe, updates dependent queries if you use Hiveku's codegen)
* Change column types (risky — existing values must cast cleanly)
* Drop columns (irreversible — data is gone)

<Warning>
  Dropping a column is permanent. Export the table first if you might need the data back.
</Warning>

## Verifying Your Table

Run a quick sanity check from the SQL editor:

```sql theme={null}
select * from your_table_name limit 5;
```

Or insert a test row:

```sql theme={null}
insert into your_table_name (title) values ('Hello world')
returning *;
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Table name rejected with &#x22;reserved word&#x22; or &#x22;invalid identifier&#x22;">
    Postgres reserves words like `user`, `order`, `group`. Either rename (`app_users`, `orders` is fine, `user_groups`) or quote the name — but renaming is almost always cleaner.
  </Accordion>

  <Accordion title="Foreign key error: referenced table or column doesn't exist">
    Create the parent table first. If referencing Supabase auth users, the exact reference is `auth.users(id)` — note the `auth.` schema prefix.
  </Accordion>

  <Accordion title="Client code gets permission denied errors">
    Row-level security is on but no policy allows the action. Either add an RLS policy (the **Owner access** template works for most user-owned tables — see [Set Up User Authentication](/how-tos/setup-auth)) or use the service role key in backend code.
  </Accordion>

  <Accordion title="Migration fails: column 'x' contains null values">
    You tried to add a `NOT NULL` column without a default, and existing rows have nothing to fill it with. Either add a default value, or make the column nullable and backfill before adding the constraint.
  </Accordion>

  <Accordion title="Can't delete a table — referenced by foreign key">
    Another table has an FK pointing at this one. Either drop that table first, drop the FK constraint on the child, or add `cascade` to the drop: `drop table your_table cascade;` (careful — cascades delete dependent rows).
  </Accordion>
</AccordionGroup>

## What's Next?

<CardGroup cols={2}>
  <Card title="Set Up User Authentication" icon="lock" href="/how-tos/setup-auth">
    Add RLS policies so users can only access their own data
  </Card>

  <Card title="Set Up the Database" icon="database" href="/how-tos/setup-database">
    Provision or connect the database itself
  </Card>
</CardGroup>
