tutorials/postgresql/export-data

PostgreSQL Export to CSV, JSON, Excel and SQL Dumps

There are two ways to get data out of PostgreSQL. Export a table, a schema or a whole database visually in 1bench, or use COPY and \copy for CSV and JSON files and pg_dump for backups you can restore.

Updated Nov 2, 2026Tested against PostgreSQL 16.13 and 18.6

Quick answer

In 1bench
Right-click the table in the sidebar, choose Export Table, pick CSV, JSON, NDJSON or SQL, and save the file. Export Schema and Export Database sit on the schema and database menus.
In SQL
sql
\copy users TO 'users.csv' WITH (FORMAT csv, HEADER)

Run it in psql. \copy writes the file on your machine and works for any role that can read the table; plain COPY TO a file writes on the server and needs extra privileges.

1bench
No SQL needed

Export visually in 1bench

1bench exports one PostgreSQL table to CSV, JSON, NDJSON or SQL, and a whole schema or database to SQL or JSON, from the sidebar's right-click menus. It does not need pg_dump installed.

Prerequisite: download 1bench. Free 7-day trial, no sign-up.

  1. 1

    Connect and expand the database

    Open your PostgreSQL connection in 1bench and expand it in the sidebar until you see the schema and its tables.

  2. 2

    Choose what to export

    Right-click a table and choose Export Table, a schema and choose Export Schema, or a database and choose Export Database. If a table is already open, the Export button at the bottom of the window exports that table.

  3. 3

    Narrow a table down (optional)

    To export only some rows, open the table, filter it in the Query panel or sort by clicking a column header, then use the Export button at the bottom of the window (the right-click menu always exports the whole table). The dialog then offers Current view, with only those rows and the visible columns, or Whole table.

  4. 4

    Pick a format

    A table exports to SQL, NDJSON, CSV or JSON; a schema or database to SQL or JSON. For SQL, tick Include structure (CREATE TABLE statements), Include data, or both. For JSON the first box reads Include column definitions.

  5. 5

    Export and save

    Click Export and choose where to save the file. A progress bar tracks the tables and rows written, Cancel stops a long export, and a message such as Exported 7 tables (1,492 rows) confirms the result.

Good to know
  • Every export reads inside one read-only transaction, so a schema or database export is a consistent snapshot and works on a read-only connection too.
  • The SQL file holds CREATE TABLE and INSERT statements, adds foreign keys and checks after the data, and resets sequences with setval. Load it back with psql; it is not a pg_restore archive.
  • Export Schema and Export Database include tables only, not views. Export Table on a view writes its definition in SQL mode and its rows in CSV or JSON.
  • Export Database covers the one database you picked, not every database on the server, and not roles. Use pg_dumpall --globals-only for roles.
  • Excel and the pg_dump custom format are not in the export menu. Export CSV for Excel, and use the pg_dump commands below for a .dump archive.
The manual way

Export with SQL and pg_dump

PostgreSQL has two COPY commands that look almost the same. is SQL: the server runs it and writes the file on the database server's disk. is a psql command: psql runs the same COPY, streams the rows to your machine and writes the file there. When the database runs on another host, in Docker or in the cloud, is almost always the one you want.

sql
\copy users TO 'users.csv' WITH (FORMAT csv, HEADER)

For backups and moving a database, writes SQL or an archive that or loads back. Every command below was run on PostgreSQL 16.13 and 18.6 against the same sample tables (, , ) and behaves the same on both unless a section says otherwise.

COPY TO vs \copy

with a file name needs superuser or the pg_write_server_files role, and the path is on the server. A normal role gets this:

text
ERROR:  permission denied to COPY to a file
DETAIL:  Only roles with privileges of the "pg_write_server_files" role may COPY to a file.
HINT:  Anyone can COPY to stdout or from stdin. psql's \copy command also works for anyone.

Even as superuser, the path must be absolute ("relative path not allowed for COPY to file") and writable by the postgres server process, or you get . has neither limit, since the file is written by your own psql process.

Runs inThe serverpsql on your machine
File lands onThe database serverYour machine
Relative pathsNot allowedRelative to where psql was started
NeedsSuperuser or pg_write_server_filesSELECT on the table
Works from other clientsYes, it is plain SQLNo, psql only

Both need SELECT on the table. Without it, either one fails with .

is the middle ground: plain SQL that any role can run, with the rows sent to the client. Redirect it in the shell:

bash
psql -d saas_app -c "COPY users TO STDOUT WITH (FORMAT csv, HEADER)" > users.csv

Export query results to CSV with headers

Put the query in parentheses where the table name goes. writes the column names as the first line.

Example: shipped orders, newest first.

sql
\copy (SELECT id, status, total_amount, created_at FROM orders WHERE status = 'shipped' ORDER BY created_at DESC) TO 'shipped.csv' WITH (FORMAT csv, HEADER)

must fit on one line, because psql ends the command at the line break. Split the query over two lines and psql reports , then sends the second line to the server as SQL and fails again. For a long query, end it with and an output format instead, which accepts any number of lines:

sql
SELECT id, status, total_amount
FROM orders
WHERE status = 'shipped'
ORDER BY id
\g (format=csv) shipped.csv

From your own shell, prints any query as CSV with a header row, so a redirect is enough:

bash
psql -d saas_app --csv -c "SELECT id, name, country FROM users ORDER BY id" > users.csv

A view cannot be the source of a bare . fails with and the hint PostgreSQL 16 says the same for a materialized view; PostgreSQL 18 copies a materialized view directly.

CSV options

These go inside after the file name, for both and :

OptionWhat it doesExample
csv, text (tab-separated, the default) or binary
Write column names first (off by default)
Column separator
Text written for NULL (empty in CSV)
Quote every value, or listed columns
Convert the file to another encoding

Example: a semicolon-separated file with every value quoted, the shape many European spreadsheet setups expect.

sql
\copy (SELECT id, name, country FROM users ORDER BY id) TO 'users_eu.csv' WITH (FORMAT csv, HEADER, DELIMITER ';', FORCE_QUOTE *)

pipes the output through a shell command, which is the easy way to compress. With the program runs on the server and needs superuser or pg_execute_server_program; with it runs on your machine.

sql
\copy users TO PROGRAM 'gzip > users.csv.gz' WITH (FORMAT csv, HEADER)

Export to JSON

turns each row into a JSON object, one per line (NDJSON). wraps all rows into one JSON array, the shape most APIs and expect.

sql
SELECT row_to_json(u) FROM (SELECT id, name, country FROM users ORDER BY id) u;
SELECT json_agg(u) FROM (SELECT id, name, country FROM users ORDER BY id) u;

Write the result with psql's unaligned, tuples-only output (), so nothing but the JSON reaches the file:

bash
psql -d saas_app -At -c "SELECT json_agg(u) FROM (SELECT id, name, country FROM users ORDER BY id) u" -o users.json

Do not send JSON through or in the default text format. It escapes backslashes a second time, so a value with a newline comes out as instead of , and any Windows path doubles its backslashes. The route above writes the JSON unchanged.

Two more traps. over zero rows returns NULL, which writes an empty file; use to get . And bigint values above 2^53 lose precision in JavaScript: 9223372036854775000 parses back as a different number, so cast such columns to text () when the file goes to a browser or Node.

picks and renames keys when the column names should not be the JSON keys.

Export to Excel

PostgreSQL has no Excel writer, so export CSV and open it in Excel, or import it with Data, From Text/CSV, which lets you set the delimiter and column types. Excel on Windows guesses the encoding, so accented names can come out garbled. Writing a UTF-8 byte order mark before the rows makes Excel read the file as UTF-8:

bash
printf '\xEF\xBB\xBF' > users.csv
psql -d saas_app --csv -c "SELECT id, name, country FROM users ORDER BY id" >> users.csv

Excel also strips leading zeros and turns long numbers such as into scientific notation when it opens a CSV directly. Import through From Text/CSV and set those columns to Text to keep them intact.

Dump a whole database with pg_dump

writes one database as SQL: the CREATE statements, the rows as COPY blocks and the sequence values. It runs as a separate program, not inside psql.

bash
pg_dump -h localhost -U demo -d saas_app -f saas_app.sql

Load it back into an empty database with psql:

bash
createdb saas_app_copy
psql -d saas_app_copy -f saas_app.sql

Since PostgreSQL 17.6, 16.10 and the other August 2025 minor releases, a plain dump starts with a line and ends with . psql understands them; a tool that runs the file as raw SQL does not, so strip those two lines or use psql. Pipe through gzip for a smaller file: .

Schema only or data only

() writes the CREATE statements without rows. () writes only the rows and the calls for sequences, for loading into tables that already exist.

bash
pg_dump -d saas_app --schema-only -f schema.sql
pg_dump -d saas_app --data-only -f data.sql

PostgreSQL 18 can also dump planner statistics with , so a restored database plans queries well before its first ANALYZE. It is off by default, and writes nothing else.

Dump a single table

picks a table, and you can repeat it or use a pattern. does the opposite and leaves a table out.

bash
pg_dump -d saas_app -t orders -f orders.sql
pg_dump -d saas_app -t 'order*' -f orders_and_items.sql
pg_dump -d saas_app -T audit_log_v2 -f without_audit.sql

A name that matches nothing fails with . dumps the table, its indexes, constraints and triggers, but not the functions a trigger calls or the tables a foreign key points to. Loading into an empty database fails at the foreign key with , and fails at its trigger with .

Custom format and pg_restore

writes a compressed archive instead of SQL. It is smaller (63 KB against 114 KB for the sample database on PostgreSQL 18) and lets pick tables, reorder work and restore in parallel.

bash
pg_dump -d saas_app -Fc -f saas_app.dump
pg_restore -l saas_app.dump

Example: restore the archive into a new database, then restore one table from it into another.

bash
createdb saas_app_restore
pg_restore -d saas_app_restore saas_app.dump

createdb orders_only
pg_restore -d orders_only -n public -t orders saas_app.dump

matches the table name in every schema, so add when two schemas both have an . It restores the table, its check constraints and its rows, but not its primary key, indexes or foreign keys; restore the whole archive, or dump with , when you need those.

Restoring over a database that already has the objects fails on the first one that exists, for example . Add to drop each object before recreating it, or to create the database named in the dump first.

The two tools do not read each other's files. psql given an archive says pg_restore given SQL says

For big databases, the directory format dumps and restores several tables at once. works only with it; fails with .

bash
pg_dump -d saas_app -Fd -j 4 -f saas_app_dir
pg_restore -d saas_app_restore -j 4 saas_app_dir

Extensions are recorded as , not copied. Restoring onto a server without the extension installed fails with , followed by an error for every table that uses its types.

Export as INSERT statements

By default the rows go out as COPY blocks, which only PostgreSQL loads. writes one INSERT per row, and names the columns too, which survives a different column order and loads into other databases more easily.

bash
pg_dump -d saas_app -t users --data-only --column-inserts -f users_inserts.sql

Each line then looks like this:

sql
INSERT INTO public.users (id, name, email, avatar_url, role, status, country, created_at, updated_at) VALUES (1, 'Mohammad Crist', '[email protected]', 'https://...', 'user', 'active', 'DE', '2024-08-28 08:25:58.535+00', '2025-10-26 23:33:05.505+00');

INSERT dumps restore far slower than COPY. batches rows into multi-row INSERTs, which cut the 120 users rows to 2 statements.

Roles and permissions

never writes roles, because roles belong to the whole server, not one database. writes the roles (with password hashes) and tablespaces, and writes just the roles.

bash
pg_dumpall -U demo --globals-only -f globals.sql
psql -d postgres -f globals.sql

Restore the globals before the databases, or every in the dump fails with an error such as . To restore without the original owners and grants, dump with .

pg_dump needs SELECT on every table and sequence it dumps. A read-only role without it stops at the first one:

text
pg_dump: error: query failed: ERROR:  permission denied for table products
pg_dump: detail: Query was: LOCK TABLE public.users, public.products, ...

Granting SELECT on the table is not enough for , since its serial column has a sequence. PostgreSQL 16 reports ; PostgreSQL 18 says . Grant both, or give the role pg_read_all_data:

sql
GRANT SELECT ON ALL TABLES IN SCHEMA public TO reporting;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO reporting;

pg_dump version mismatch

pg_dump must be the same major version as the server or newer. Version 16 against a PostgreSQL 18 server refuses to run:

text
pg_dump: error: aborting because of server version mismatch
pg_dump: detail: server version: 18.6 (Debian 18.6-1.pgdg13+2); pg_dump version: 16.13 (Debian 16.13-1.pgdg12+1)

Install the newer client, or run pg_dump inside the server's own container. Going down a version is where the trouble is: pg_restore 16 cannot read an archive written by pg_dump 18 (). A plain SQL dump from pg_dump 18 loads into 16, though its line fails with , which is harmless.

From a Docker container

When PostgreSQL runs in Docker, run pg_dump inside the container and redirect its output to a file on your machine. The container's pg_dump always matches the server version.

bash
docker exec my-postgres pg_dump -U demo -d saas_app -Fc > saas_app.dump
docker exec -i my-postgres pg_restore -U demo -d saas_app_restore < saas_app.dump

Leave out . With a terminal attached, Docker turns line endings into and corrupts a binary archive, and restoring it then fails with . The same pattern works for CSV: .

Work with PostgreSQL in one window

1bench is a desktop GUI for PostgreSQL. Browse tables, filter rows, run SQL and export what you see, with the SQL still there when you want it.

Open PostgreSQL in 1bench

Frequently asked questions

Should I use pg_dump or COPY to export a PostgreSQL table?
Use COPY or \copy when you want the rows in a file another tool reads, such as CSV for a spreadsheet or a data pipeline. Use pg_dump when the file goes back into PostgreSQL, since it carries the CREATE TABLE, indexes, constraints and sequence values along with the data. pg_dump -t orders --data-only sits between the two: rows only, but in a form psql can load.
Is pg_dump safe to run on a live database?
Yes. pg_dump reads from one snapshot and takes only ACCESS SHARE locks (its LOCK TABLE ... IN ACCESS SHARE MODE shows up in error output), so reads and writes carry on while it runs. What it does block is DDL: an ALTER TABLE or DROP TABLE on a dumped table waits until the dump finishes. On a busy server, run it against a replica or off-peak.
How do I export all tables in a PostgreSQL database to CSV?
There is no single command for it, so loop over pg_tables in the shell: for t in $(psql -d saas_app -Atc "SELECT tablename FROM pg_tables WHERE schemaname = 'public'"); do psql -d saas_app -c "\copy public.$t TO '$t.csv' WITH (FORMAT csv, HEADER)"; done. Each table lands in its own file named after it. Each \copy runs in its own session, so the files are not one consistent snapshot; use pg_dump if that matters.
Can pg_dump export only rows that match a WHERE clause?
No. pg_dump has no row filter, and pg_dump --where fails with "unrecognized option '--where'". Export the rows with \copy (SELECT * FROM orders WHERE status = 'shipped') TO 'shipped.csv' CSV HEADER, or copy them into a scratch table with CREATE TABLE ... AS SELECT and dump that table with -t.
What is the difference between pg_dump and pg_basebackup?
pg_dump is a logical backup: SQL or an archive of one database that restores into any newer PostgreSQL version and lets you pick tables. pg_basebackup copies the whole cluster's data files byte for byte, so it restores only into the same major version, but together with WAL archiving it supports point-in-time recovery. Most teams keep both for different jobs.