Skip to content
SparquetSparquet

Outputs

A pipeline writes one destination with output, or several with outputs. Each destination is configured independently, and they all start from the same transformed DataFrame.

{
"outputs": [
{ "format": "delta", "path": "analytics.orders", "mode": "overwrite" },
{ "format": "csv", "path": "/exports/summary", "columns": ["id", "revenue"] }
]
}
Key Type Default Purpose
format string connector, lowercased at parse
path string path, table, view or topic
mode string overwrite write behavior
partition_by list [] physical partitioning, file formats only
columns list all column projection for this destination
options object {} connector options
transformations list [] reshaping for this destination only
Mode Behavior
overwrite replaces the destination
append adds rows
merge upsert — Delta and Iceberg

merge requires options.merge_keys:

{
"format": "delta",
"path": "analytics.orders",
"mode": "merge",
"options": {
"merge_keys": ["order_id"],
"merge_condition": "T.deleted = false"
}
}

T is the target table, S the incoming DataFrame.

columns selects what this destination writes, without touching the others:

{
"outputs": [
{ "format": "parquet", "path": "/dw/orders_full" },
{ "format": "csv", "path": "/exports/finance", "columns": ["order_id", "revenue", "country"] }
]
}

When destinations need different shapes — not just different columns — give each one its own chain. It runs after the validations, on that destination only.

{
"outputs": [
{
"format": "kafka",
"path": "orders-events",
"transformations": [
{ "type": "struct", "column": "payload", "fields": { "id": "order_id", "total": "revenue" } },
{ "type": "with_column", "column": "value", "expression": "to_json(payload)" }
],
"options": { "bootstrap_servers": "broker:9092", "value_column": "value" }
},
{
"format": "delta",
"path": "analytics.customer_revenue",
"mode": "merge",
"transformations": [
{
"type": "group_by",
"by": ["customer_id"],
"agg": ["sum(revenue) as revenue", "count(*) as orders"]
}
],
"options": { "merge_keys": ["customer_id"] }
}
]
}

Every transformation type is available here, including join and runtime {{variables}}.

For each destination, in this order:

  1. its own transformations
  2. the columns projection
  3. the write

Which means a projection sees the columns the destination’s own transformations produced, not the ones the main chain ended with.

Temp views turn a set of pipelines into a composable job: several pipelines write the same staging view, and a final one validates and publishes it.

// pipeline 1..N
{ "output": { "format": "view", "path": "orders_staging", "mode": "overwrite" } }
// final pipeline
{ "input": { "format": "view", "path": "orders_staging" } }

All of them must run in the same Spark session — the same process — which is exactly what Sparquet does when you call run() several times on one instance.

result = fw.run("pipeline.json")
result.rows_written # total across every destination
for m in result.output_metrics:
print(m.format, m.mode, m.path, m.rows_written)

Each destination reports its own OutputMetrics (format, path, mode, rows_written) in result.output_metrics. The count is taken on that destination’s final DataFrame — after its own transformations and column projection, just before the write — so it is exact even when a per-destination chain explodes or aggregates rows.