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.

  1. Dashboard → your project → Database tab → Open Studio.
  2. Open the Table Editor.
  3. 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 text where you wanted numeric or timestamptz.
  4. 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:

  1. Add your public key under Dashboard → your project → Connect tab → SSH & CLI.

  2. Open the tunnel and leave it running:

    ssh -N -L 5432:127.0.0.1:5432 [email protected]
    
  3. 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

  1. 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.

  2. Enable RLS on every imported table in public and add policies, or the tables are readable through the Data API. See Row Level Security.

  3. Update statistics so the planner has accurate row counts: analyze;

  4. 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.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us