Supabase
Supabase gives you a hosted PostgreSQL database with a generous free tier and a browser-safe client. This guide takes you from an empty Supabase project to a live CRUD screen, step by step. No backend to run.
Try it first: the Supabase demo is exactly this - paste your project URL + anon key and it runs against your real database in the browser.
Want it all in one page? The Supabase CRUD grid tutorial is the complete, copy-paste walkthrough - the table, the schema, the data source, and the full grid page with create / edit / delete, start to finish. This page is the deeper reference behind it.
There are two ways to connect, and you can use either:
| Path | When | Runs in |
|---|---|---|
A. supabase-js client |
SPAs, and when you want Row-Level Security + auth enforced per user | The browser |
| B. SvelteKit server route | You already use SvelteKit +server.ts and want SQL on the server |
Node / edge server |
Path A is what the demo uses and what most people want. Start there.
Step 1 - Create a project
- Sign in at supabase.com and click New project.
- Give it a name and a database password, pick a region, and wait ~2 minutes for it to provision.
Step 2 - Create and seed the table
Open SQL Editor in the Supabase dashboard, paste this, and click Run. It
creates a customers table and adds a few rows.
create table if not exists customers (
id bigint generated always as identity primary key,
name text not null,
email text not null,
tier text,
mrr integer,
active boolean default true
);
insert into customers (name, email, tier, mrr, active) values
('Ada Lovelace', '[email protected]', 'enterprise', 1200, true),
('Alan Turing', '[email protected]', 'pro', 240, true),
('Grace Hopper', '[email protected]', 'enterprise', 980, true),
('Edsger Dijkstra', '[email protected]', 'free', 0, false);
Step 3 - Open access with Row-Level Security
Supabase blocks all access by default until you add a policy. Because the anon key is public (it ships in the browser), RLS is what actually protects your data - so you always keep it on and grant exactly what you intend.
For a public demo, allow the anon role to read and write:
alter table customers enable row level security;
create policy "svgrid demo access" on customers
for all to anon using (true) with check (true);
For a real app, do not grant blanket access to
anon. Grant toauthenticatedand scope rows to the signed-in user, e.g.using (auth.uid() = user_id). See the Supabase RLS docs.
Step 4 - Get your keys
In the dashboard: Project Settings -> API. Copy two values:
- Project URL -
https://xxxxxxxxxxxx.supabase.co - anon public key - a long token starting with
eyJ...
The anon key is designed to be shipped in the browser. Never ship the service_role key to a client - it bypasses RLS.
Path A - Connect from the browser (supabase-js)
This is a SvelteKit page (or any Svelte SPA). Install the client:
npm i @supabase/supabase-js
Put your keys in .env (Vite exposes PUBLIC_-prefixed vars to the browser):
PUBLIC_SUPABASE_URL="https://xxxxxxxxxxxx.supabase.co"
PUBLIC_SUPABASE_ANON_KEY="eyJhbGciOiJI..."
Build the data source with createSupabaseDataSource - it maps the grid's
sort, filter, paging, and CRUD onto the Supabase query builder for you. You bring
the client (so RLS + auth apply per request); it's the whole data layer:
// src/lib/customers.source.ts
import { createClient } from '@supabase/supabase-js'
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
import { createSupabaseDataSource } from '@svgrid/enterprise'
import { customersSchema, type Customer } from '$lib/customers'
const client = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY)
export const customersSource = createSupabaseDataSource<Customer>({
client,
table: 'customers',
schema: customersSchema,
// searchColumns: ['name', 'email'], // optional; defaults to text + enum fields
})
Then wire it exactly like the in-memory source from Getting started - the page code is identical:
const controller = createServerDataSource(customersSource, {
pageSize: 10, optimistic: true, getRowId: (r) => String(r.id),
onChange: (s) => (view = s),
})
controller.refresh()
That is the entire integration. Sorting, filtering, paging, and CRUD now run against hosted Postgres, with RLS enforced on every request.
Don't know the columns ahead of time?
introspectSupabaseTable({ url, key, table })reads them from PostgREST and returns anEntitySchemayou can feed toschemaToColumnsandcreateSupabaseDataSource- that's how the live demo adapts to any table. It also reads foreign keys, so a FK column comes back as arelationfield and the form gets a searchable picker automatically - see Relations.
Want it live? Add
createSupabaseRealtimeto refetch and flash rows whenever the database changes - no polling.
Path B - SvelteKit server route (connection string)
Prefer to keep SQL on the server? Supabase is plain Postgres, so use the pg
driver with the connection pooler string (Dashboard -> Project Settings ->
Database -> Connection string, port 6543). The Studio CLI writes the whole
route for you:
npm i pg
export DATABASE_URL="postgres://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres"
npx @svgrid/studio add customers --db supabase --url "$DATABASE_URL"
npm run dev # open /customers
This generates the schema, a +server.ts API route backed by
createSqlDataSource, and the page. Keys stay on the server;
the browser only talks to your own route. See The Studio CLI and
Databases for the full picture.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
| On connect it shows no data and only offers to "add columns" - even though the table has columns and rows | The demo infers your columns from the rows it fetches, so an RLS-blocked read means it sees neither rows nor columns and falls back to manual entry. Add the select policy from Step 3, then reconnect. Confirm it's RLS by running select * from your_table; in the SQL Editor (that bypasses RLS): if it returns rows but the demo shows none, RLS is the cause. |
| Grid is empty, no error | RLS is on but no policy grants select. Add the policy in Step 3, or scope one to your users. |
new row violates row-level security on create |
Your policy allows select but not insert. Use for all (as above) or add a with check policy for writes. |
| "Could not reach Supabase" | Wrong Project URL, or a network / CORS issue. Confirm the URL is the https://xxxx.supabase.co form, not the database host. |
| 401 / invalid key | You pasted the wrong key. Use the anon public key from Settings -> API, not the service_role or the JWT secret. |
| Numbers arrive as strings | Postgres numeric comes back as text over PostgREST. Use integer / bigint for numeric columns, or cast in a view. |
More answers in Troubleshooting & FAQ.
See also
- Supabase demo - this guide, live
- Databases - the SQL sources and dialects
- Data binding - the
ServerDataSourcecontract - Getting started - the page code, end to end