Secure connections
Save PostgreSQL connection details with encrypted passwords, SSL settings, URL import, testing, search, edit, and delete actions.
Export PostgreSQL to JSON
PostgreSQL can build JSON in SQL, which makes exporting it mostly a question of shape: one array containing everything, or one object per line. The second scales and the first does not.
Short answer: Use json_agg with psql's -t -A flags for a single JSON array — simple, and fine up to a few hundred thousand rows. For anything larger use JSON Lines: one row_to_json object per line, which streams instead of building the whole document in memory. Avoid plain COPY TO for JSON, because its text format escapes backslashes and corrupts the output.

PgDeck is shaped around PostgreSQL tasks that happen every day: connect, inspect, filter, edit, query, manage structure, copy, and export.
Save PostgreSQL connection details with encrypted passwords, SSL settings, URL import, testing, search, edit, and delete actions.
Browse schemas and tables, search the table list, pin favorites, filter rows, sort columns, resize columns, and paginate results.
Run SELECT and non-SELECT statements from multi-tab SQL editors with syntax highlighting and interactive query results.
Edit cells, add rows, duplicate rows, set NULL, stage deletes, review pending changes, and commit everything in one transaction.
json_agg collects rows into one array. The psql flags matter: -t drops the column header and row-count footer, and -A turns off the column alignment padding that would otherwise corrupt the JSON.
psql -t -A -d mydb -c "SELECT json_agg(t) FROM (SELECT id, email, created_at FROM users WHERE active) t" > users.jsonrow_to_json uses your column names as keys. When you want different names, a nested shape, or only some fields, build the object explicitly with json_build_object — it is clearer than aliasing columns and it lets you nest.
SELECT json_agg(json_build_object(
'id', u.id,
'email', u.email,
'signedUpAt', u.created_at,
'orderCount', (SELECT count(*) FROM orders o WHERE o.user_id = u.id)
))
FROM users u;json_agg builds the entire document in memory on the server before returning it, so a large table can exhaust memory or simply take a long time. JSON Lines — one object per line, no wrapping array — streams row by row and is what most data tools expect for bulk import anyway.
psql -t -A -d mydb -c "SELECT row_to_json(t) FROM (SELECT * FROM events) t" > events.jsonlCOPY's default text format escapes backslashes, so any \n or \" inside your JSON strings comes out doubled and the file no longer parses. If you must use COPY, the CSV format with a quote character that cannot appear in JSON avoids the mangling.
-- Corrupts embedded backslashes:
COPY (SELECT row_to_json(t) FROM users t) TO STDOUT;
-- Safe:
COPY (SELECT row_to_json(t) FROM users t) TO STDOUT
WITH (FORMAT csv, QUOTE E'\x01', DELIMITER E'\x02');For a one-off, filtering a table in a client and exporting the result as JSON avoids all of the above. PgDeck exports the current filtered view or a query result to JSON, CSV, or SQL INSERT statements, so what you export matches what you were looking at.
json stores the text as written, preserving key order, whitespace, and duplicate keys. jsonb parses into a binary form, which reorders keys, removes duplicates, and normalises whitespace. For exports intended to be diffed or compared byte-for-byte, that difference matters — jsonb_agg will not give you back the document you put in.
row_to_json renders a timestamptz in PostgreSQL's output format, which depends on the session TimeZone setting and is not ISO 8601 with a T separator. Consumers expecting strict ISO input will reject it. Format it explicitly with to_char, or cast in the query, rather than fixing it downstream.
A single array must be complete before anything can read it, so the producer holds it all in memory and the consumer parses it all at once. JSON Lines is one self-contained object per line: it streams, it can be split across files or workers, a partial file is still valid up to the last complete line, and it appends. Most bulk-import tools prefer it.
Exporting orders with their line items as a nested array is one query with a correlated json_agg subquery, rather than two exports and a join in application code afterwards. Building the shape in SQL keeps the export reproducible and moves the work to the database, which is generally where it belongs.
SELECT * into row_to_json exports every column including password hashes, session tokens, and internal flags, and JSON makes the field names self-documenting for whoever ends up with the file. Name your columns, or build the object explicitly. It costs a few seconds and it is the difference between an export and a leak.
More on choosing, comparing, and running a PostgreSQL desktop client.
Wrap the query in json_agg and run it through psql with -t and -A so the output is not padded or decorated: psql -t -A -c "SELECT json_agg(t) FROM (SELECT ...) t" > out.json. For large result sets, use row_to_json per row to produce JSON Lines instead.
Three usual causes. Missing -t so psql included a header and row count. Missing -A so columns are padded with spaces. Or you used COPY TO in its default text format, which escapes backslashes and breaks any embedded \n or \" inside strings.
row_to_json converts one row into one JSON object, so a query returns many objects. json_agg aggregates all of those into a single JSON array. Use row_to_json for JSON Lines output and json_agg when you want one array document.
Use JSON Lines rather than json_agg. json_agg assembles the entire array on the server before returning anything; row_to_json emits one object per row and streams. JSON Lines files also split cleanly and remain valid up to the last complete line.
json preserves key order, whitespace, and duplicate keys exactly as written. jsonb normalises all three. If the export will be compared or diffed, use json; if you only need valid data, either works.