How to convert json to postgresql insert statements
- Step 1Export your JSON as an array of objects — The tool expects a top-level JSON array of flat objects:
[{"id":1,...},{"id":2,...}]. A single object ({"id":1,...}) also works — it is treated as a one-row table. If your API wraps rows under a key like{"data":[...]}, extract the inner array first with json-path-extractor using$.data[*]. - Step 2Drop the file onto the converter — Drag a
.jsonfile (or paste-saved file) onto the dropzone above..xlsx/.xls/.odsare also accepted — the first sheet is read into a JSON array automatically. A raw.csvis not parsed as rows here; convert it first with csv-to-json. - Step 3Set the table name and pick PostgreSQL — Type the target table into Table name (default
data). Click the PostgreSQL dialect button so identifiers are double-quoted and booleans render asTRUE/FALSE. The table name is quoted too, soorderoruser(reserved words) are safe. - Step 4Exclude columns you don't want seeded — Put internal or audit keys in Exclude columns as a comma-separated list, e.g.
internal_id, _meta, __v. Excluded keys are dropped from both theCREATE TABLEdefinition and everyINSERTrow, so the generated schema matches exactly what you seed. - Step 5Choose CREATE TABLE and conflict behaviour — Leave Include CREATE TABLE on for a fresh table; turn it off to generate INSERT-only against an existing schema. IF NOT EXISTS guards re-runs. Enable ON CONFLICT DO NOTHING when re-seeding a table that already holds rows so unique/PK collisions are skipped instead of aborting.
- Step 6Generate, review, and run with psql — Click Generate SQL, then Copy or Download SQL. Review the inferred types against your real schema (decimals you intend as
NUMERIC(10,2)will show asDOUBLE PRECISION). Run withpsql -d mydb -f output.sql. The stats line reports rows, columns, and statement count so you can sanity-check the row total.
Real PostgreSQL options and what they emit
Every control on the converter mapped to the exact SQL it produces in PostgreSQL mode. Defaults shown are the values the tool ships with.
| Option | Effect in PostgreSQL output | Default |
|---|---|---|
| Table name | Sets the quoted target, e.g. CREATE TABLE IF NOT EXISTS "users" and INSERT INTO "users" | data |
| Dialect = PostgreSQL | Double-quoted identifiers; booleans as TRUE/FALSE; decimals typed DOUBLE PRECISION | PostgreSQL (selected) |
| Exclude columns | Comma-separated keys removed from CREATE TABLE and every INSERT row (e.g. _meta, internal_id) | empty |
| Include CREATE TABLE | Prepends the CREATE TABLE block; off = INSERT statements only | On |
| IF NOT EXISTS | Adds IF NOT EXISTS to CREATE TABLE so re-runs don't error on an existing table | On |
| ON CONFLICT DO NOTHING | Appends ON CONFLICT DO NOTHING to each INSERT (no conflict target, table-wide skip) | Off |
How JSON value types map to PostgreSQL column types
Type is inferred per key from all non-null values across every row. A single non-matching value drops the column to a looser type, and any key with only nulls becomes TEXT.
| JSON value(s) for a key | Inferred PostgreSQL type | Example value in INSERT |
|---|---|---|
All true/false | BOOLEAN | TRUE / FALSE |
All whole numbers (1, 42) | INTEGER | 42 |
Any decimal (9.5, 0.01) | DOUBLE PRECISION | 9.5 |
| Strings, dates, UUIDs | TEXT | '2026-01-01' |
Nested object {...} | TEXT (JSON-stringified) | '{"plan":"pro"}' |
Array [...] | TEXT (JSON-stringified) | '["a","b"]' |
null (every row) / key absent | TEXT; value emitted as NULL | NULL |
Free vs Pro limits for the JSON to SQL converter
This is a Pro tool. Numbers come directly from the tier-limits table for the json family. Free-tier preview is metered by a code-generation quota.
| Limit | Free | Pro |
|---|---|---|
| Max input file size | 2 MB | 100 MB |
| Files per run | 1 | 10 |
| Rows per INSERT batch | 100 (fixed) | 100 (fixed) |
| Processing location | In-browser | In-browser |
Cookbook
Real JSON in, real PostgreSQL out — copied verbatim from the converter so you can see exactly how types, quoting, and conflict handling resolve.
Lookup-table seed with apostrophes and booleans
ExampleA categories array with a name containing an apostrophe and an active flag. Note the doubled single quote and the BOOLEAN column with TRUE/FALSE — both Postgres-correct.
Input:
[
{"id": 1, "name": "O'Brien & Co", "active": true},
{"id": 2, "name": "Acme", "active": false}
]
Output (PostgreSQL):
CREATE TABLE IF NOT EXISTS "categories" (
"id" INTEGER,
"name" TEXT,
"active" BOOLEAN
);
INSERT INTO "categories" ("id", "name", "active")
VALUES
(1, 'O''Brien & Co', TRUE),
(2, 'Acme', FALSE);Nested object becomes a TEXT column
ExampleA settings object is not expanded into columns — it is JSON-stringified into one TEXT field. Store it as-is and parse with Postgres JSON functions later, or normalise upstream first.
Input:
[{"id": 1, "settings": {"theme": "dark", "beta": true}}]
Output (PostgreSQL):
CREATE TABLE IF NOT EXISTS "prefs" (
"id" INTEGER,
"settings" TEXT
);
INSERT INTO "prefs" ("id", "settings")
VALUES
(1, '{"theme":"dark","beta":true}');
-- To query later, cast: settings::jsonb ->> 'theme'
-- (change the column type to JSONB in your real schema)Idempotent re-seed with ON CONFLICT DO NOTHING
ExampleRe-running a seed against a table that already has rows aborts on a duplicate primary key. Enabling the conflict toggle appends a table-wide skip so the script is safe to run twice.
Config: ON CONFLICT DO NOTHING enabled
Output (PostgreSQL):
INSERT INTO "roles" ("id", "name")
VALUES
(1, 'admin'),
(2, 'editor') ON CONFLICT DO NOTHING;
-- Note: no conflict target like (id). It is a table-wide
-- DO NOTHING, not an upsert. For real upserts you must
-- edit in ON CONFLICT (id) DO UPDATE SET ... by hand.Excluding audit columns before seeding
ExampleAPI exports often carry internal keys you don't want in the seed. List them in Exclude columns and they vanish from both the schema and the rows.
Input:
[{"id": 1, "email": "a@x.com", "_meta": "trace-123", "__v": 0}]
Config: Exclude columns = _meta, __v
Output (PostgreSQL):
CREATE TABLE IF NOT EXISTS "users" (
"id" INTEGER,
"email" TEXT
);
INSERT INTO "users" ("id", "email")
VALUES
(1, 'a@x.com');Ragged rows: union of keys, missing values as NULL
ExampleWhen rows have different keys, the column set is the union of all keys across rows. Any row missing a key gets NULL for it — no error.
Input:
[
{"id": 1, "name": "A", "tier": "pro"},
{"id": 2, "name": "B"}
]
Output (PostgreSQL):
CREATE TABLE IF NOT EXISTS "accounts" (
"id" INTEGER,
"name" TEXT,
"tier" TEXT
);
INSERT INTO "accounts" ("id", "name", "tier")
VALUES
(1, 'A', 'pro'),
(2, 'B', NULL);Errors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
ON CONFLICT has no conflict target
By designThe conflict toggle emits ON CONFLICT DO NOTHING with no (column) target — a valid table-wide skip in Postgres, but it requires the table to have a way to detect conflicts (a primary key or unique constraint). It is not an upsert. If you need ON CONFLICT (id) DO UPDATE SET ..., edit the generated SQL by hand; the tool does not produce upserts or specify targets.
Decimal columns inferred as DOUBLE PRECISION
ExpectedAny non-integer number makes the whole column DOUBLE PRECISION. For money you almost certainly want NUMERIC(10,2) to avoid float rounding. The tool cannot know your intent, so edit the CREATE TABLE type after generating, or pre-store currency as integer cents in the JSON.
Nested object/array stored as TEXT, not JSONB
By designObjects and arrays are JSON.stringify-ed into a TEXT column, not JSONB or a child table. The value round-trips as a JSON string. To query it, change the column type to JSONB in your real schema, or flatten the JSON first with json-flattener so each nested key becomes its own column.
Top-level JSON is not an array or object
Invalid inputThe input must parse as a JSON array of objects, or a single object. A bare string, number, or a JSON array of primitives ([1,2,3]) will not produce a meaningful table — [1,2,3] is treated as three rows with no keys, yielding an empty column set. Wrap scalars in objects ([{"value":1}]) first.
Empty array input
ExpectedAn empty array [] produces the comment -- Empty array, no statements generated and zero statements rather than an error. Check the rows count in the stats line; if it reads 0, your extraction step probably returned nothing.
Mixed integer and decimal in one column
ExpectedIf a key holds 1 in one row and 1.5 in another, the column is typed DOUBLE PRECISION because not every value is an integer. This is correct, but it means a column you expected to be INTEGER may widen the moment a single decimal appears in the data.
CSV file dropped instead of JSON
Invalid inputRaw .csv text is passed straight to JSON.parse and fails — only .json and spreadsheet files (.xlsx/.xls/.ods, read as their first sheet) become rows. Convert CSV to a JSON array first with csv-to-json, then run it here.
Reserved-word column or table names
SupportedKeys like order, user, or select are double-quoted in PostgreSQL output, so reserved words are safe. The same applies to the table name. This is one reason to keep the PostgreSQL dialect selected for Postgres targets rather than copying MySQL-style backtick output.
Input over the tier file-size limit
Upgrade requiredFree tier caps input at 2 MB; Pro raises it to 100 MB. A larger seed file is rejected before conversion. Split the JSON array into chunks, or use the conflict-safe re-seed pattern to load it in passes.
Frequently asked questions
Does this generate a primary key or indexes?
No. The CREATE TABLE it emits has only column names and inferred types — no PRIMARY KEY, NOT NULL, UNIQUE, or index clauses. Add those by hand after generating, or run the inserts against a table you already created with the right constraints (turn off Include CREATE TABLE for that case). The ON CONFLICT DO NOTHING option does rely on a primary key or unique constraint existing on the target table to have any effect.
How are single quotes and apostrophes escaped?
By doubling them, which is the SQL-standard escape Postgres uses: O'Brien becomes 'O''Brien'. There is no backslash escaping. This is applied to every string value (and to the JSON-stringified form of nested objects/arrays), so apostrophes in names, notes, and descriptions never break a statement.
Can it do an upsert / ON CONFLICT DO UPDATE?
No. The only conflict option is ON CONFLICT DO NOTHING with no target column — a table-wide skip of conflicting rows. There is no DO UPDATE SET, no merge, and no way to specify the conflict column. If you need a true upsert, generate the inserts here and edit them into ON CONFLICT (your_key) DO UPDATE SET ... manually.
Why is my numeric column DOUBLE PRECISION instead of NUMERIC?
Type inference only distinguishes integers from other numbers. Any decimal value types the column DOUBLE PRECISION. For exact-precision needs (currency, financial data) edit the generated CREATE TABLE to NUMERIC(p,s), because DOUBLE PRECISION is a binary float and will introduce rounding.
What happens to nested JSON objects and arrays?
They are serialised with JSON.stringify and stored as a single TEXT value, e.g. '{"plan":"pro"}' or '["a","b"]'. They are not exploded into columns or child tables. To get one column per nested key, run the JSON through json-flattener first; to query the stored JSON in Postgres, change the column type to JSONB in your schema.
How big can the INSERT batches get?
Rows are grouped 100 per multi-row INSERT ... VALUES statement. This is fixed — there is no batch-size control in the UI. For a 1,000-row array you get ten INSERT statements (plus the CREATE TABLE). The stats line after generating reports the exact statement count.
Can I generate INSERT statements without a CREATE TABLE?
Yes — turn off Include CREATE TABLE. You then get only the INSERT INTO statements, which is what you want when the table already exists with your own constraints and types. Combine this with ON CONFLICT DO NOTHING for repeatable seeds into an existing schema.
Does it support a JSON object wrapped under a data key?
Not directly — it expects the top-level value to be the array (or a single object). If your export looks like {"data":[...]}, pull the inner array out first with json-path-extractor using $.data[*], then feed the resulting array here.
Is my data uploaded anywhere?
No. Conversion runs entirely in your browser via the bundled logic — the JSON, the generated SQL, and any production values in it never reach a server. Only an anonymous run counter is recorded for signed-in dashboard stats, with no content.
Can I seed multiple tables from one file?
No — one run produces one table. The whole array goes into the single table you name. For a multi-table seed, split your JSON by entity and run the converter once per table (changing the Table name each time), then concatenate the resulting .sql files in dependency order.
How do columns get ordered in the output?
By first appearance across the rows: the tool takes the union of all keys in the order they are first seen. If row 1 has id, name and row 2 adds tier, the column order is id, name, tier. Reorder keys in your source JSON if you need a specific column order.
What file types can I drop in besides .json?
Spreadsheet files (.xlsx, .xls, .ods) are accepted — the first sheet is converted to a JSON array of row objects automatically, then turned into SQL. A raw .csv is not parsed as rows; convert it with csv-to-json first. If you only need a different output format, see json-to-csv or json-to-yaml.
Privacy first
Conversion runs locally in your browser. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.