Skip to content
Docs

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.

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.

JSON in the grid: hover preview, side panel, format, save — JSON cells pretty-print in a hover card, expand into a side panel or full screen, and edit in place — Format re-indents and Save writes back through the ordinary mutation pipeline.
  • 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.
Images and PDFs stored as blobs, previewed and downloaded — The attachments table keeps files in a blob column: expanding a row previews photos inline, row navigation walks to a PDF, and Download saves it as a real file.

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 linksGET /_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 — one k.<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.

Terminal window
# 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.