JSON Documents & File Attachments
Database cells are not always short scalars. The grid’s content pipeline detects what a value actually is — a JSON document, XML, a long text, or a binary file — and gives each its own viewing and editing surface, without leaving the table you were exploring.
JSON documents
Section titled “JSON documents”A cell is treated as JSON when its column is a native json/jsonb type, maps
to the JSON GraphQL scalar, or when its text content parses as JSON — so
JSON stored in ordinary TEXT columns (the common SQLite/PostgreSQL pattern)
gets the same treatment.
- Hover a JSON cell and the document pretty-prints in a preview card.
- Expand opens the side panel (480px), with a one-click full-screen mode for large documents; Up/Down walks the same column across rows.
- Edit in place: Format pretty-prints, and Save writes back through the same mutation path as any other cell edit — no special JSON endpoint.
Images and documents stored in the database
Section titled “Images and documents stored in the database”Blob columns are fetched on demand (the grid never selects large values into the page query), and an expanded binary value is sniffed by its magic bytes — never by a column name or a stored mime string, so a mislabeled blob can only downgrade to the generic rendering, never masquerade as an image:
- PNG, JPEG, GIF, and WebP render inline in the content panel and in the record form’s binary field.
- PDFs and unknown blobs show a typed size card.
- Every binary value has a Download button that saves a real file with the sniffed extension.
Binary links — the second encoding
Section titled “Binary links — the second encoding”Blob content is available two ways, and both share one read path and one security model:
- Base64 through GraphQL — what the grid and content panel consume. Every read path materializes blob columns as base64 at a single conversion point.
- Direct binary links —
GET /_blob/{table}/{column}?k.<pkColumn>=…streams the raw bytes. The identity projects through the same shared gate as every other transport, and the read runs a query intent through the same executor, so tenant isolation, soft-delete, and policy guards apply identically; a pipeline denial answers the same 404 as a missing row. Composite keys are addressed in full — onek.<column>parameter per primary-key column.
The link endpoint advertises Accept-Ranges: bytes and serves single-range
requests as 206 Partial Content, so large files download in resumable
windows — something base64-in-JSON cannot offer. Inline rendering is limited
to a magic-byte-verified image allowlist served with nosniff; everything
else, PDFs included, downloads as an attachment so stored markup can never
execute on the app origin. Responses are capped by MaxBlobBytes
(default 32 MiB) — over the cap is an explicit 413, never a truncated body.
# The photo stored in attachments row 1, as a plain URL:curl -O 'http://127.0.0.1:5000/_blob/attachments/content?k.attachment_id=1'# Resume a large download from byte 1048576:curl -H 'Range: bytes=1048576-' '…/_blob/attachments/content?k.attachment_id=1'Mount it with app.UseBifrostBlobs(...); the workbench desktop host serves it
by default.
The IoT Sensors Quick Start schema ships with both: JSON documents in its
specifications, metadata, and configuration columns, and real PNG photos
and PDF documents in its attachments table.
Related
Section titled “Related”- Data workbench overview
- File storage — the server-side upload / download surface over file-tagged columns.