GoShipped
Product

πŸ—„ Database & Storage

Postgres, pgvector, migrations, and Supabase Storage. Add product tables the same way GoShipped already does.

You don’t need to rebuild this part. Add your tables next to chat.

GoShipped uses Supabase Postgres for app data, pgvector for embeddings, and Supabase Storage for uploaded files. The FastAPI app talks to Postgres with SQLAlchemy. You add tables with migrations β€” not by clicking around in production.

supabase/migrations/     schema (source of truth)
        ↓
pnpm supabase:push       hosted
pnpm supabase:start      local (applies automatically)
        ↓
apps/api/app/db/         models + session

What is already there

Migrations in supabase/migrations/ β€” apply all of them, in filename order.

AreaWhat you get
Userspublic.users, filled from Auth (handle_new_user)
Chatconversations, messages, tool_calls
RAGuploaded_files, uploaded_file_chunks + vector search
Memoryagent_memory + match_agent_memory
Billingcustomers, subscriptions, webhook event ids
Settings / onboardinguser_settings
Usage & safetyusage events, rate-limit counters

pgvector is enabled in the first migration. Embedding columns are vector(1536) (OpenAI text-embedding-3-small).

supabase/seed.sql is empty on purpose.

How the API uses the database

PiecePath
URLDATABASE_URL in apps/api/.env (postgresql+asyncpg://…)
Sessionapps/api/app/db/session.py
Modelsapps/api/app/db/models.py

The API uses the database role in DATABASE_URL. That path is not PostgREST. RLS still matters for anything that hits Supabase from the browser; user-owned tables use auth.uid() = user_id. Billing, usage, and similar tables often allow SELECT and keep writes on the API.

Apply migrations

Use the Supabase CLI already pinned in the repo. Do not invent a second migration path.

WhereCommand
Local stackpnpm supabase:start (applies automatically)
Reset local datapnpm supabase:reset
New filepnpm supabase:new -- add_widgets
Hosted / linked projectpnpm supabase:push
Setup / deploy wizardssupabase db push after they know the DB URL

Do not run migrations inside the Vercel build. Push with the CLI (or paste SQL in the dashboard, still in filename order) before the API needs the new columns. See database migrations if the CLI workflow is new.

Storage bucket is not a migration

File uploads use a private bucket named chat-files (FILE_STORAGE_BUCKET). Create it in the Supabase Storage dashboard (or API). Setup/doctor expect it when features.files is on. See RAG & Files.

Add a product table

Same pattern as conversations and files:

  1. pnpm supabase:new -- add_widgets
  2. Table + indexes + ENABLE ROW LEVEL SECURITY + policies
  3. Mirror the columns in apps/api/app/db/models.py
  4. Service + route under apps/api/app/
  5. Local: reset or push. Hosted: pnpm supabase:push
CREATE TABLE IF NOT EXISTS public.widgets (
    id UUID PRIMARY KEY DEFAULT extensions.uuid_generate_v4(),
    user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
    title TEXT NOT NULL,
    metadata JSONB NOT NULL DEFAULT '{}',
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX IF NOT EXISTS idx_widgets_user_id ON public.widgets(user_id);

ALTER TABLE public.widgets ENABLE ROW LEVEL SECURITY;

CREATE POLICY widgets_select_own ON public.widgets
    FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY widgets_insert_own ON public.widgets
    FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY widgets_update_own ON public.widgets
    FOR UPDATE USING (auth.uid() = user_id);
CREATE POLICY widgets_delete_own ON public.widgets
    FOR DELETE USING (auth.uid() = user_id);

Keep API handlers scoped to CurrentUser.id. Do not expose a generic table to the browser and hope RLS is enough if you have not tested the policies.

Storage

FactDetail
Bucketchat-files (private)
Who uploadsFastAPI with the service role (apps/api/app/services/storage_service.py)
Object pathusers/{user_id}/conversations/{conversation_id}/{file_id}/{filename}
NeedsSUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, features.files=true

You do not configure bucket RLS for that server path. Create extra buckets the same way if your product stores other files β€” still prefer the API as the writer. Official reference: Supabase Storage.

Local vs hosted

Hosted (typical): setup links the project and pushes migrations. No Docker. Create a project in the dashboard if you need one.

Local: pnpm supabase:start needs Docker. Auth mail goes to Mailpit. See local development for the CLI stack itself.

Either way, supabase/ in the repo is the schema. The dashboard is for Auth URLs, SMTP, and creating Storage buckets β€” not a second source of truth for tables.

On this page