Beetl Docs
Connect your data

File upload

CSV, NDJSON and Parquet files, uploaded or dropped in object storage.

File upload is the shortest path from a spreadsheet export to a queryable dataset. Drag a file into the browser and Beetl creates a File connection and a Bronze dataset from it.

The mapping is one to one, and it is worth internalising early: one uploaded file is one File connection is one dataset. Ten files means ten connections, not one connection with ten files in it. Combining them is a pipeline job, not an upload job.

Accepted formats

Format is decided by the filename extension, lowercased.

ExtensionDataset type
.csvCsv
.parquetParquet
.json, .jsonl, .ndjsonJson

Anything else is rejected with UnsupportedUploadedFileFormat. There is no sniffing and no fallback to a binary type, so a CSV saved as .txt fails rather than landing as an unusable blob. Rename it and retry.

The three JSON extensions map to one dataset type. Newline-delimited JSON is the shape that reads cleanly.

The upload size limit

64 MiB per file

Both upload routes carry an axum body limit of 67,108,864 bytes, set by FILE_UPLOAD_BODY_LIMIT_BYTES in crates/beetl-server/src/config.rs. This is a hard limit on the HTTP body. It is not configurable per tenant, and there is no chunked or resumable upload path around it.

A file over the limit is rejected by the transport layer, before any Beetl code runs, so the error you see is a body-size rejection rather than a Beetl error code. Split the file, or convert it to Parquet first. Parquet compression frequently takes a CSV that will not fit under the limit and puts it comfortably under.

Uploading

Right-click anywhere on the empty canvas and choose New connection.

The Beetl canvas right-click menu, showing New pipeline, New connection and Add zone
Right-click the empty canvas. The same menu creates pipelines and zones.

Pick File Upload. The dialog lists every system type Beetl supports, the same seven the source matrix is generated from.

The New Connection dialog listing seven system types: PostgreSQL, Webhook, HTTP Feed, File Upload, MS Dynamics 365 BC, SAP Business One and Server-Sent Events
Every system type Beetl supports. This list is the seven SystemType variants.

Drag one or more files in. The browser accepts .csv, .json, .jsonl, .ndjson and .parquet.

Name each dataset. The name is suggested from the filename but you own it, and it is required. An optional description sits next to it.

Submit. The browser sends one POST per file to /web/-/connections/files, in sequence.

Bulk upload is a client-side loop, not a backend batch. Partial success is real: if file seven of ten fails, the first six are created and re-submitting the remainder picks up where it stopped.

Server-side, the ingest service owns the policy. It computes a SHA-256 checksum and byte size, extracts the Arrow schema, creates the Bronze dataset, uploads the bytes, and rolls the dataset back if any of those steps fail. Uploaded files write in Append mode.

Each File connection then carries first-class metadata: original_filename, file_format, byte_size, checksum_sha256, data_set_id, storage_location, uploaded_at and replaced_at.

Replacing a file

Open the connection and use the Replace control on the Uploaded File card. It posts to /web/-/connections/{connection_id}/file.

Replacement is schema-checked in the domain and rejects three cases: a connection that is not a File connection or is not Active, a file-type mismatch, and an Arrow SchemaMismatch. On rejection the old file stays active and queryable, so a bad replace does not cost you the dataset. A successful replace is recorded as a durable FileUploadRecorded event.

The schema check means a monthly export that gains a column will be rejected. That is deliberate. Handle it by creating a new connection rather than by forcing the swap.

The Replace control is hard to reach

It lives only on the connection detail page at /web/connections/{id}, and nothing in the navigation links there. You reach it by typing the URL or via the Data Landscape graph.

Known limits

  • Web only. The public API upload endpoint was removed. POST /api/connections rejects system_type: File with FILE_CONNECTIONS_WEB_ONLY, and POST /api/ingest/{source_id} rejects File connections with NOT_APPLICABLE. There is no scriptable upload path.
  • 64 MiB, hard. As above.
  • No data preview. The success state is a count of created connections. To see what landed, query the dataset.
  • Thin error reporting. Since the 2026-08-19 overhaul the bulk flow shows a shared spinner and a per-file "Uploaded" marker, with all errors collapsed into one page-level alert. Which file failed and why is not always obvious.
  • Name collisions are checked client-side only. The uniqueness suggestion runs in the browser. There is no server-side recheck at commit.
  • Object store first. With no object store registered for the tenant, uploads fail with 422 OBJECT_STORE_NOT_CONFIGURED.

On this page