Importing data (CSV, SQL dumps)
Load data into a SupaHost project from CSV files, SQL scripts or pg_dump archives, using Studio, a supabase-js script, psql and pg_restore over the Business SSH tunnel, or the Supabase Cloud import.
How you load data into a project depends on its format, its size, and your plan. This page covers each route and the clean-up steps that apply to all of them.
Pick a method
| You have | Size | Method | Plans |
|---|---|---|---|
| A Supabase Cloud project | Any | One-click import | All |
| A CSV file | Small to medium | Studio table editor | All |
A .sql script |
Small to medium | Studio SQL editor | All |
| CSV or JSON from another system | Any | Script through the Data API | All |
A pg_dump archive or large SQL file |
Large | pg_restore or psql over the SSH tunnel | Business, Enterprise |
Direct Postgres connections from your computer are only possible on Business and Enterprise, through the SSH tunnel. On other plans, use Studio, the Data API, or the MCP server's execute_sql for small batches.
From Supabase Cloud
If the data is in a Supabase Cloud project, use the built-in import instead of moving it by hand. It copies the database in one transaction, Auth users with their passwords and identities, RLS policies and roles, and Storage buckets and files into an empty SupaHost project. Start it from Dashboard → your project → Import tab. Auth settings, Edge Functions, cron jobs and Vault secrets aren't copied; the import lists them so you can set them up again.
Full steps: Importing from Supabase Cloud.
CSV in Studio
Studio's table editor can read a CSV with a header row and insert it into a table.
- Dashboard → your project → Database tab → Open Studio.
- Open the Table Editor.
- To create a new table from the file, start a new table and choose to import data from a CSV. Studio suggests column names and types from the header and the first rows. Check the types before saving; it's easy to end up with
textwhere you wantednumericortimestamptz. - To add rows to an existing table, open the table and use Insert → import from CSV. The CSV header must match the column names.
Studio parses the file in your browser, so very large files are slow or fail. For big imports, split the file or use a script.
SQL scripts in Studio
For a .sql file of create table and insert statements, paste it into Studio's SQL Editor and run it. Statements in one run execute together, so a failure part-way through rolls back the whole run.
For a bookings app seed:
insert into public.venues (name, city, capacity) values
('Riverside Hall', 'Leeds', 120),
('The Glasshouse', 'Bristol', 60),
('North Pier Studio', 'Brighton', 35);
Keep each run to a manageable size: a few thousand rows of inserts is fine; a multi-gigabyte dump belongs on the tunnel route below.
Scripted inserts through the Data API
On any plan, a small Node.js script can read a file and insert it in batches with the service_role key, which bypasses RLS. Run it on your own machine or server, never in a browser.
import { readFileSync } from 'node:fs'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://app-one.acme.supahost.space',
process.env.SUPABASE_SERVICE_ROLE_KEY!,
{ auth: { persistSession: false } }
)
type Booking = { external_id: string; venue_id: number; starts_at: string; guests: number }
const rows: Booking[] = JSON.parse(readFileSync('bookings.json', 'utf8'))
const BATCH = 500
for (let i = 0; i < rows.length; i += BATCH) {
const chunk = rows.slice(i, i + BATCH)
const { error } = await supabase
.from('bookings')
.upsert(chunk, { onConflict: 'external_id' })
if (error) throw new Error(`Batch at row ${i}: ${error.message}`)
console.log(`Imported ${Math.min(i + BATCH, rows.length)} / ${rows.length}`)
}
Upserting on a unique column (external_id here) makes the script safe to re-run after a failure. Batches of a few hundred rows keep each request small.
pg_restore and psql over the SSH tunnel
On Business and Enterprise you can connect standard Postgres tools to your project's database. Set up the tunnel first, as described in SSH access and direct Postgres:
Add your public key under Dashboard → your project → Connect tab → SSH & CLI.
Open the tunnel and leave it running:
ssh -N -L 5432:127.0.0.1:5432 [email protected]Copy the connection string from the Connect tab → Database → Show connection string (owner only). It looks like
postgresql://postgres:<password>@127.0.0.1:5432/p_app_one.
Restore a custom-format dump (pg_dump -Fc) into the project:
export DATABASE_URL='postgresql://postgres:<password>@127.0.0.1:5432/p_app_one'
pg_restore --no-owner --no-privileges --schema=public \
--dbname "$DATABASE_URL" invoices.dump
--no-owner and --no-privileges skip ownership and grants from the source server, whose roles don't exist here. --schema=public keeps the restore away from the auth and storage schemas, which Supabase services own.
Load a plain SQL file or a large CSV with psql:
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f schema_and_data.sql
psql "$DATABASE_URL" -c "\copy public.invoices (number, customer_id, total, issued_on) from 'invoices.csv' with (format csv, header true)"
\copy streams the file from your computer, so it handles files of any size.
If you prefer not to run a tunnel, pipe a SQL file through the restricted SSH command, which runs psql as postgres on the server:
ssh [email protected] sudo supahost-psql app-one < schema_and_data.sql
After any import
Reset sequences. Rows inserted with explicit ids don't move the sequence forward, so the next insert fails with a duplicate key. Fix it per table:
select setval( pg_get_serial_sequence('public.invoices', 'id'), coalesce((select max(id) from public.invoices), 0) + 1, false );More in duplicate key value violates unique constraint after importing data.
Enable RLS on every imported table in
publicand add policies, or the tables are readable through the Data API. See Row Level Security.Update statistics so the planner has accurate row counts:
analyze;Reload the Data API schema cache if new tables don't appear:
notify pgrst, 'reload schema';See PostgREST not seeing new tables.
Common import errors
| Error | Cause |
|---|---|
invalid byte sequence for encoding "UTF8": 0x00 |
Null bytes in text. See the KB article. |
role "..." does not exist |
Dump references source roles; use --no-owner --no-privileges. |
extension "..." is not available |
The extension isn't in this Postgres image. Check pg_available_extensions. |
permission denied for schema auth |
The dump touches Supabase-owned schemas; restore public only. |
Your project's database counts toward the plan's shared database size, so check headroom under Dashboard → Usage & billing before a very large import.