Examples

Real-world patterns showing how ExCSV builds on plain CSV. Examples use version=0.6 — plain, sidecar, ZIP, pack, and JSON.

1. Minimal

Just the header line. Everything else is standard CSV.

#!excsv version=0.6 rows=3
name,age,city
Alice,32,Berlin
Bob,28,Tokyo
Carol,45,Lagos

Any CSV reader ignores the # line and reads the data normally.

2. Sidecar for plain CSV

Metadata in sales.excsv; rows stay in sales.csv.

#!excsv version=0.6 delim=comma header=1 rows=2 reference=sales.csv
#@source: sales_db.orders
#column name=id type=int
#column name=customer type=string
#column name=amount type=decimal unit=USD
#%sum: ,,750.50

sales.csv (data sibling)

Ordinary CSV — any CSV tool reads this.

id,customer,amount
1,Acme Corp,500.00
2,Globex Inc,250.50

See Sidecar. Metadata only — the rows stay in the CSV.

3. Typed columns with constraints

Column schema defines types, validation rules, and display hints.

#!excsv version=0.6 delim=comma header=1 rows=3
#column name=id type=int unique=1
#column name=email type=string required=1 len_max=254 pattern=^[^@]+@[^@]+$
#column name=score type=decimal min=0 max=100 format=0.0
#column name=active type=boolean default=true
id,email,score,active
1,alice@example.com,92.5,true
2,bob@example.com,87.3,true
3,carol@example.com,64.0,false

Parsers can validate data against pattern, min/max, and required constraints.

4. Tab-delimited with metadata

A TSV export from a database, with provenance metadata.

#!excsv version=0.6 delim=tab header=1 rows=3 encoding=UTF-8
#@source: warehouse_db.inventory
#@author: ops-team@company.com
#@exported: 2026-03-20T08:00:00Z
#@tool: pg_dump/16.2
#column name=sku type=string unique=1
#column name=name type=string
#column name=qty type=int min=0
#$dql-postgres: SELECT sku, name, qty FROM inventory WHERE qty > 0
sku	name	qty
WDG-001	Widget Pro	1200
WDG-002	Widget Lite	3400
GDG-001	Gadget X	870

The query that produced the file is preserved as #$dql-postgres. The raw text is taken to end of line — SQL syntax preserved as-is.

5. Aggregations

Pre-computed statistics embedded in the file.

#!excsv version=0.6 delim=comma header=1 rows=5
#column name=region type=string
#column name=product type=string
#column name=revenue type=decimal unit=USD
#column name=units type=int
#%count_nonnull: 5,5,5,5
#%sum: ,,48250.00,1450
#%avg: ,,9650.00,290
#%min: ,,3200.00,120
#%max: ,,18500.00,520
region,product,revenue,units
North,Widget,18500.00,520
South,Widget,12000.00,380
North,Gadget,8750.00,210
South,Gadget,5800.00,220
West,Widget,3200.00,120

Aggregation lines use the file's delimiter. Empty fields mean "not applicable" for that column.

6. SQL companions — single dialect via header

Set sql-dialect once, drop the suffix on every #$ line.

#!excsv version=0.6 delim=comma header=1 rows=3 sql-dialect=postgres
#@source: analytics_db.users
#column name=id type=long unique=1
#column name=email type=string required=1
#column name=created_at type=datetime
#$ddl: CREATE TABLE users (id BIGSERIAL PRIMARY KEY, email TEXT NOT NULL UNIQUE, created_at TIMESTAMPTZ NOT NULL)
#$ddl: CREATE INDEX users_created_at_idx ON users(created_at)
#$ddl: GRANT SELECT ON users TO readonly
#$dql: SELECT * FROM users WHERE created_at >= '2026-01-01'
id,email,created_at
1,alice@example.com,2026-01-15T09:30:00Z
2,bob@example.com,2026-02-20T14:00:00Z
3,carol@example.com,2026-03-01T11:45:00Z

Each #$ddl line is one statement. They execute in file order. #$dql is informational provenance — never executed automatically.

7. SQL companions — multi-dialect

Same data, three flavors of CREATE TABLE. Pick the one matching your target.

#!excsv version=0.6 delim=comma header=1 rows=3
#column name=id type=long unique=1
#column name=amount type=decimal unit=USD
#$ddl: CREATE TABLE orders (id INTEGER PRIMARY KEY, amount DECIMAL(10,2))
#$ddl-mysql: CREATE TABLE orders (id BIGINT PRIMARY KEY AUTO_INCREMENT, amount DECIMAL(10,2)) ENGINE=InnoDB
#$ddl-postgres: CREATE TABLE orders (id BIGSERIAL PRIMARY KEY, amount NUMERIC(10,2))
#$ddl-clickhouse: CREATE TABLE orders (id UInt64, amount Decimal(10,2)) ENGINE = MergeTree() ORDER BY id
#$ddl-sqlite: CREATE TABLE orders (id INTEGER PRIMARY KEY AUTOINCREMENT, amount REAL)
id,amount
1,99.99
2,250.00
3,42.50

A consumer targeting mysql executes only the unqualified line and the -mysql line — others are skipped. The unqualified line acts as a portable fallback.

8. Zipped container header

Header of the inner .excsv file inside a sales.excsv.zip archive.

#!excsv version=0.6 delim=comma header=1 rows=10000 checksum=sha256:e3b0c442... original-size=204800 sql-dialect=postgres
#@source: warehouse.sales
#@created: 2026-04-01T08:00:00Z
#@tool: excsv-cli/0.2.0
#column name=order_id type=long unique=1
#column name=customer_id type=long
#column name=amount type=decimal unit=USD
#$ddl: CREATE TABLE sales (order_id BIGSERIAL PRIMARY KEY, customer_id BIGINT, amount NUMERIC(10,2))
order_id,customer_id,amount
1,42,99.99
2,13,250.00
3,77,42.50

Inside a .excsv.zip, the header records the uncompressed size as original-size. The same prefix is copied into the ZIP comment, so you can preview schema and stats without unzipping. See ZIP.

9. Pack manifest

_manifest.excsv inside sales.excsv.pack.zip (v0.6).

#!excsv version=0.6 layout=pack table-count=2 original-size=2363200
#@pack-name: sales-q1-2026
#@source: warehouse-snapshot
#table name=orders dir=orders/ columns=4 original-size=1843200
#table name=customers dir=customers/ columns=2 original-size=512000
#fk from=orders.customer_id to=customers.id

Each table is a folder: schema in _header.excsv, one .col file per column. See Pack.

10. No header row (header=0)

Columns defined by index instead of matching header names.

#!excsv version=0.6 delim=comma header=0 rows=4
#column index=0 type=datetime
#column index=1 name=level type=string
#column index=2 name=message type=string
2026-03-24T10:15:32Z,INFO,Application started
2026-03-24T10:15:33Z,INFO,Connected to database
2026-03-24T10:15:35Z,WARN,Cache miss ratio above threshold
2026-03-24T10:16:01Z,ERROR,Connection timeout on retry 3

With header=0, index is required. name is optional — the first column here is anonymous.

11. Custom attributes (x- prefix)

Application-specific metadata using the x- convention.

#!excsv version=0.6 delim=comma header=1 rows=3
#@tool: my-crm/2.4.0
#@comment: Customer export for marketing sync
#column name=id type=int unique=1 x-crm-entity=contact
#column name=name type=string title="Full Name" x-crm-field=display_name
#column name=email type=string required=1 x-crm-field=primary_email
#column name=segment type=string x-crm-field=market_segment separator=|
#column name=ltv type=decimal unit=USD x-dashboard-widget=sparkline
id,Full Name,email,segment,ltv
101,Alice Park,alice@acme.co,enterprise|active,12400.00
102,Bob Chen,bob@globex.io,smb,3200.50
103,Carol Diaz,carol@initech.com,enterprise|churned,8750.00

Custom x- attributes are ignored by generic parsers but carry meaning for specific tools. Note how the header row uses title ("Full Name") for columns that have it.

12. JSON form (.excsv.json)

The same document as JSON — for APIs, config, and LLM structured output.

{
  "excsv": "0.6",
  "csv": { "delim": "comma", "header": true },
  "meta": { "source": "hr_db.employees", "grain": "one row per employee" },
  "columns": [
    { "index": 0, "name": "id", "type": "int", "role": "id", "unique": true },
    { "index": 1, "name": "name", "type": "string" },
    { "index": 2, "name": "dept", "type": "string", "role": "dimension", "enum": ["Engineering", "Marketing", "Design"] },
    { "index": 3, "name": "salary", "type": "decimal", "unit": "USD", "role": "measure", "agg": "sum" }
  ],
  "aggregates": { "sum": [null, null, null, "285000.00"] },
  "rows": 3,
  "data": [
    [1, "Alice", "Engineering", "120000.00"],
    [2, "Bob", "Marketing", "85000.00"],
    [3, "Carol", "Design", "80000.00"]
  ]
}

Every # line has a key: the header is csv, #@ is meta, #column is columns (each stored column has index for its position in data rows — a virtual computed column has none), #% is aggregates. decimal values stay strings so they don't turn into floats. Validate against the JSON Schema; see JSON.

13. Quoting (quote=double)

Quote fields that contain the delimiter or embedded double quotes.

#!excsv version=0.6 delim=comma quote=double header=1 rows=2
#column name=name type=string
#column name=note type=string description="contains ""special"" chars"
name,note
Alice,"She said ""hello"" to everyone"
Bob,"Line1, then more"

With quote=double, wrap values in " when they contain commas or quotes; escape a literal quote by doubling (""). Simple values like Alice stay unquoted.

14. Null handling

Default null (empty field) vs custom null marker.

#!excsv version=0.6 delim=comma header=1 rows=4 null=NA
#column name=sensor type=string
#column name=reading type=decimal
#column name=status type=string
sensor,reading,status
TMP-01,22.5,ok
TMP-02,,offline
TMP-03,NA,ok
TMP-04,19.8,NA

Both empty fields and NA are treated as null. Without null=NA, only empty fields would be null.

15. LLM-friendly: grain, enum, role, agg

Semantic hints that let an AI assistant reason about the table without guessing.

#!excsv version=0.6 delim=comma header=1 rows=4
#@source: sales_db.orders
#@grain: one row per order
#column name=order_id type=long unique=1 role=id
#column name=status type=string role=dimension enum=pending|completed|cancelled
#column name=amount type=decimal unit=USD role=measure agg=sum
#column name=balance type=decimal unit=USD role=measure agg=avg
#column name=created_at type=datetime role=time
#%count_nonnull: 4,4,4,4,4
#%sum: ,,1050.50,,
order_id,status,amount,balance,created_at
1,completed,500.00,1200.00,2026-01-15T09:30:00Z
2,completed,300.50,900.00,2026-02-20T14:00:00Z
3,pending,200.00,700.00,2026-03-01T11:45:00Z
4,cancelled,50.00,650.00,2026-03-10T16:20:00Z

#@grain states what one row is. enum gives the closed value domain for status. role tells a consumer how to use each column — group by dimension, aggregate measure, never sum an id — and agg flags additivity: amount sums, but balance (agg=avg) must not be summed across time. All are advisory hints; none change how a plain CSV reader sees the data.