Skip to main content

The app manifest

bda.manifest.json sits at the root of a hand-built app's bundle next to app.js and app.css. It declares everything the app is allowed to do. An unknown field fails the upload (the manifest is strict), so the field list below is the whole contract. Composed apps get their manifest derived from the description; you never edit it by hand.

The fields​

FieldWhat it isLimit
appIdThe app id Studio gave you at dataapp_start. Required.
entryapp.js
styles["app.css"]
titleThe app's title.
queriesThe declared semantic queries the app may run (below).32
functionsImports: functions and workflows the app may call, pinned (below).16
chat{ "enabled": true, "anchors": [...] }. Chat is on by default and drawn by Studio.
viewsviews.tabs: the tabs a schedule or a workflow screenshot can capture.20 tabs
analysesDeclared Detect and Explain analyses (see Detect and Explain).16
cacheA small shared key-value space for this app (values up to 64 KB, expiring). Anything undeclared is refused (store_not_declared).
blobsNamed read-only files the builder uploads (lookup tables, targets, model weights).16, up to 100 MB each
calendarThe app's time zone and week start, used for windows and schedules.
telemetryWhat the frame reports about itself.
agentsOlder app-declared agents with connector grants. Prefer an Agent function imported under functions.8
notebooksDatasets exported to notebooks.
lookupsRetired. Existing apps keep working; never add one. Read a connection with an agent instead.

The field list is generated from Studio's own manifest model and kept in the agent guide: app manifest fields.

A declared query​

{
"id": "failed_orders_by_reason",
"sql": "SELECT reason, failed_orders FROM m_retail_demo WHERE day >= :from AND day < :to ORDER BY failed_orders DESC",
"parameters": [
{ "name": "from", "type": "date", "required": true },
{ "name": "to", "type": "date", "required": true }
],
"columns": [
{ "name": "reason", "type": "string" },
{ "name": "failed_orders", "type": "number" }
],
"maxLimit": 1000
}
  • id: lower snake case; what the app asks for.
  • sql: one SELECT, at most 8000 characters, no semicolons. Parameters are :name or $name; every placeholder must be declared (at most 16). Metrics are columns the model already defines; never aggregate them yourself; one model; a bounded time range; no joins, no OR.
  • columns: the allow-list for filtering and sorting (at most 64).
  • maxLimit: 1 to 10000 rows.

Prove every query with query_run before declaring it: a query that does not compile makes the whole version invalid, and the upload error is less helpful than the one query_run gives.

Imports​

"functions": {
"order_kpis": { "ref": "fn:<tenant>/order_kpis@3" },
"weekly_digest": { "ref": "wf:<tenant>/weekly_digest@2" }
}
  • The key is the local name the code uses. The value is { "ref": ... } only.
  • A reference is pinned to a version (@3); @latest is never allowed in a manifest.
  • A function must be shared with your workspace and exposed to apps; a workflow must be published.
  • Publishing a version that adds or re-pins an import is confirmed by a person.
  • The functions a Code function needs must be declared by the app: a Code function that lists a semantic.query capability names query ids, and the SQL for those ids lives in the calling app's queries. This is easy to miss: the function carries no SQL of its own.

Chat​

"chat": { "enabled": true, "anchors": ["panel", "selection"] }

Studio draws the chat. The app reports each card (title, kind, query, a digest of what is shown) through the template's Panel component so chat can talk about what is on screen. Whether chat may call the app's imports is an app setting (Functions in chat), not a manifest field.

Tabs​

"views": { "tabs": [ { "id": "default", "title": "Overview" }, { "id": "reasons", "title": "By reason" } ] }

A schedule or a workflow screenshot names the tab it captures (tabs: ["default"] for an app without tabs).

Persistence​

"cache": { "ttl_max_s": 86400 },
"blobs": [ { "name": "targets", "purpose": "Target per measure, set by the PM" } ]

The cache is shared by every viewer of the app and expires; the frame has no viewer identity, so nothing per person lives there. Blobs are uploaded by the builder (design_blob_upload for composed apps, the blob tools for hand-built ones) and read by anyone who can open the app. Never put personal data, secrets or unapproved files in either.

Calendar​

"calendar": { "timezone": "Asia/Kolkata", "week_start": "monday" }

Drives the app's default windows, "last week" in Detect and Explain, and schedule times.

Bundle limits​

  • Zip up to 25 MiB; each asset up to 2 MiB; the build emits exactly app.js and app.css.
  • Keep a function call's input under 64 KB of JSON; larger inputs belong in a blob.

Errors you will meet​

ErrorMeaningFix
query_not_allowedthe app asked for a query the manifest does not declaredeclare it, upload a new version
store_not_declareda cache or blob the manifest does not declaredeclare it
fn_not_declaredcode named a function the manifest does not importadd the import under functions
upload invalid with per-query errorsa declared query does not compilefix the SQL; run it with query_run first
an unknown fieldthe manifest is strictremove the field

More in Troubleshooting.