When you need to seed a GraphQL API or import a spreadsheet of records, writing one mutation per row by hand is slow. This tool reads your CSV and emits a GraphQL document with one createX call per row, inferring the right scalar type for every value — so strings are quoted, numbers and booleans are bare, and empty cells become null.
How it works
- Parse the CSV with an RFC 4180 parser that respects quoted fields containing commas, newlines and escaped
""quotes. The first row supplies the field names. - Infer each value’s type so the GraphQL is syntactically correct:
- empty →
null true/false(case-insensitive) → boolean- a finite number → numeric literal
- anything else → a double-quoted string, with
"and\escaped
- empty →
- Emit the mutation. Each row becomes
mutationField(arg1: value1, arg2: value2) { id }. You can output one operation per row, or alias all rows into a single document (row0: createUser(...) { id }) so the batch runs in one request.
mutation Seed {
row0: createUser(name: "Ada", age: 36, active: true) { id }
row1: createUser(name: "Linus", age: 54, active: false) { id }
}
Single operations vs. aliased batch document
Two output modes serve different needs:
Single operation per row generates one named mutation CreateX { ... } per CSV
row. This is useful when you want to paste and run them individually, or when your
GraphQL server does not allow multiple root fields in one operation. The downside is
that you must execute them one at a time.
Aliased batch document combines all rows into a single mutation Seed { row0: ..., row1: ..., row2: ... }. The server receives one request and executes all mutations in it, making seeding much faster. The aliases (row0, row1, …) also let you identify individual successes and failures in the response, since GraphQL returns each alias as a separate key.
Most production GraphQL servers support aliased multi-field mutations, but some have depth limits or disable batching by default — check your server’s settings if the batched document is rejected.
Type inference edge cases
The inference rules follow GraphQL scalar conventions:
| CSV cell | Inferred GraphQL type | Reasoning |
|---|---|---|
| (empty) | null | No value |
true or false | Boolean | Case-insensitive |
42 or -3.14 | Int / Float | Finite JSON-style number |
007 | String | Leading zero implies identifier, not number |
1,000 | String | Comma formatting is not a GraphQL number |
"hello, world" | String | Embedded comma preserved by RFC 4180 parser |
The rule for leading-zero preservation is intentional. Postcodes, phone extension numbers, and product codes that look numeric should stay as strings. If you need one treated as a number, remove the leading zero in the source CSV.
Adapting the output for input types
The generated mutations use positional scalar arguments directly. If your schema
uses an input type — for example createUser(input: CreateUserInput!) — you need
to wrap the generated arguments:
# Generated (flat arguments)
createUser(name: "Ada", age: 36) { id }
# Adapted for input type
createUser(input: { name: "Ada", age: 36 }) { id }
This is a two-second edit after generation. Alternatively, rename the CSV header row to match your schema’s exact field names so the generated arguments align without any manual correction.
Tips and notes
- Header names are used verbatim as argument names — make sure they match your schema’s input fields (or rename the CSV header row).
- Quote a numeric-looking value in the CSV (e.g. a postcode or phone number) by wrapping it in
"…"if you want it kept as a string; otherwise it is inferred as a number. - The selection set defaults to
{ id }; change it after generation if your mutation returns a different shape. - Everything runs locally, so the CSV’s contents stay on your device.