The manifest.
app/manifest.json is the app's own declaration of its data and its settings. It ships inside app/, so the code and the definitions it depends on always travel together. You write the definitions; operators fill in the values in the portal.
The five sections
{
"datasets": [
{ "key": "products", "name": "Products", "access": "ro", "api": "rw", "needForBoot": true },
{ "key": "orders", "partition": "shop", "retention": { "days": 400 }, "size": "200MB" }
],
"settings": [
{ "key": "currency", "caption": "Currency", "type": "select", "options": ["GBP", "EUR"], "default": "GBP" },
{ "key": "till_no", "caption": "Till number", "type": "number", "perDevice": true }
],
"files": { "needForBoot": ["logos"] },
"partition": {
"caption": "Shop",
"required": true,
"options": [ { "value": "11", "caption": "High Street" }, { "value": "15", "caption": "Market Square" } ]
},
"screen": { "orientation": "landscape", "writable": { "portal": false } }
}
| Section | What it declares | Page |
|---|---|---|
datasets | The collections of records the app reads and writes. | Datasets and sync |
settings | The fields an operator fills in; the app reads them as Arda.config. | Settings |
files | Which folders of the project's files must be on the device before the app starts. | Files |
partition | What a device's data partition may be: a caption, whether it is required, a list of choices. | Partitions |
screen | Which way a device draws its screen — portrait, landscape, or as it is turned — and whether a device may be set differently. | below |
Every section is optional. An app with no manifest still runs, but its project has no datasets and no settings: the portal does not create either, they come only from the manifest.
datasets
| Key | Meaning | Default |
|---|---|---|
key | The name the app asks for: Arda.db.dataset('orders'). Letters, digits, . _ -, starting with a letter or digit, up to 80 characters. outbox is reserved. | required |
access | "rw" or "ro": whether devices may push records into it. | "rw" |
api | "rw", "ro" or "none": the most the HTTP API may do with it. | "rw" |
partition | The record field a device's data partition selects on. | none (every device gets every record) |
needForBoot | true: the app does not start until this dataset is current — from v1.1.17 on a device's very first start too. See when the app starts. | false |
retention | "never", {"days": N} or {"space": true}. | "never" |
size | A storage ceiling: bytes, or "50MB", "1GB". | the account's allowance |
name, description | What the portal shows. | the key |
settings
| Key | Meaning |
|---|---|
key | Required. Lower case: a letter, then letters, digits or _, up to 64. Keys beginning arda_ belong to the player and are refused. |
type | text (the default), memo, number, bool, select, color, list, image, rows. Each is described on Settings. |
caption, help | The label and the help text in the portal's form. |
default | Seeds the project's value once, when the field is first created. It must fit the type. |
perDevice | true: a device may override the project's value. |
writable | Who may change the value: {"portal": true, "api": true, "device": false} when absent, and a member left out keeps that default. portal: false locks it in the portal, api: false in the HTTP API; device: true lets the app write this device's own value with Arda.config.set (implies perDevice; refused on an arda_ key and on a rows setting with a secret field). See Writing a setting from the app. device: from v1.1.17 |
options | For select (required there): bare words, or {"value", "caption"} — or {"device": "printers"} to offer the device's own list (the settings page, from v1.2.1). |
unit | For number: shown beside the box ("s", "%"). |
min, max, item, fields | For rows only. See rows. |
files
One key, needForBoot: "*" (every file), one folder name, or a list of folder names under /storage/. Those folders are fetched first and the app starts once they are present. Absent: no file holds the app back.
partition
| Key | Meaning |
|---|---|
caption | What the portal's device setup calls the field (Shop, Branch). Data partition when absent. |
required | true: a device or an install code with no partition is refused. |
options | Turns the text box into a choice. Either a list — {"value", "caption"} or a bare word (both value and caption); values are always strings: 31 becomes "31" — or a dataset to read the choices from (below). |
The portal uses the block only when the project's Setup has Data partition: use the app's list turned on; it is off by default, and then a device's partition is typed in. Without the block, the portal shows a free text box. A device whose partition was set before the block existed keeps its value; one the options do not name is shown as "31 (not in the app's list)".
The choices from a dataset
Instead of a list written in the manifest, options may name a dataset whose records are the choices. Opening a branch is then a record (sent through the HTTP API or edited in the portal), not a commit.
{
"datasets": [
{ "key": "branches", "access": "ro" },
{ "key": "prices", "partition": "branch" }
],
"partition": {
"caption": "Branch",
"required": true,
"options": { "dataset": "branches", "value": "id", "caption": "name" }
}
}
datasetnames a dataset declared in the same manifest, and that dataset must not be partitioned itself: the pack fails otherwise.valuenames the record field that holds a device's partition value;captionthe field a person picks by.captionis optional and defaults to the value field.- The choices are that dataset's current records. A record without the value field (or with an empty one, or one over 80 characters) is skipped; when two records have the same value, the first one counts; the list is sorted by caption, at most 1000 choices.
- As with a written list, the project's Setup must have Data partition: use the app's list turned on (off by default). Off, the partition is a free text box whatever the manifest says.
- A device whose partition is no longer in the list keeps it, and the portal marks it as not in the app's list. Nothing moves a device's partition for it.
options written as an object. A deployed app on such a device runs with its whole manifest ignored (no needForBoot, no dataset clean-up), and a developer-mode run refuses to start. The written list works on every version.
"options": {"device": …} before the fleet runs v1.2.1
ArdaPlayer up to v1.2.0 cannot read a select's options written as an object, and a deployed app on such a device runs with its whole manifest ignored, the same way. The written list works on every version.
screen
Which way a device draws its screen — for tablets, and anything else that can turn. The block presets every device of the project; a device's Setup › Android in the portal, or PATCH /v1/devices/{uid}/policy in the HTTP API, may choose differently for one device unless the block locks it.
| Key | Meaning |
|---|---|
orientation | portrait, landscape, or auto — the screen turns as the device is turned, which is also what a device does with no block at all. |
writable | Who may set a device differently: {"portal": true, "api": true} when absent, and a member left out keeps that default. portal: false locks the row in the portal, api: false refuses it in the HTTP API. A locked orientation is the manifest's on every device, whatever was saved for the device before. |
{
"screen": { "orientation": "landscape", "writable": { "portal": false, "api": false } }
}
An Android device turns its screen to it from ArdaPlayer v1.3.0 (Android devices); a Linux device keeps the fact and draws as before. A player that does not know the key logs it and ignores it, so the block is safe to ship ahead of the fleet.
How it is checked
- Strictly by ArdaForm. When a commit is packed, an unknown key, a wrong type or a bad value fails the pack, and every fault is listed at once. No device is told about a release whose manifest is wrong.
- Leniently by the player. A key the player does not know is logged and ignored, so a manifest written for a newer platform never stops an older device. A key it knows, written in a shape it cannot read, is different: that device ignores the whole manifest (the case of
partition.optionsas a dataset on players up to v1.1.16, and of a select'soptionsas{"device": …}on players up to v1.2.0). - In developer mode the player watches the file: a save takes effect within about two seconds, and a broken save keeps the last good one.
When the manifest changes
- Additive automatically, destructive by hand. Packing a commit creates missing datasets and setting fields and updates their definitions, before any device is told. Nothing is ever deleted and no value is touched.
- A dataset the new manifest no longer names becomes an orphan in the portal: its records are kept, and the portal offers a Delete. Devices stop syncing it when they swap to the new version.
- A setting's
defaultonly seeds a value when the field is created. Changing it later does not change what operators have set. - A setting's
keyis written into your code; treat it as permanent. Renaming it is a new setting and an orphaned old one. - On a project whose app has a manifest, the portal edits values only; definitions come from the file.
"device": true), which sends its manifest as it connects. Until then the portal's Data tab has nothing to show and the HTTP API cannot write to the dataset. Records a device pushed in the meantime are held on the device and retried, not lost. See the order of operations.
Never put secrets in it
The manifest is in your repository and inside every device's copy of the app. Passwords, tokens and keys belong in settings an operator fills in (for a password, a secret field of a rows setting), not in a default.
Checked: the example above and every rule in the tables were checked against ArdaForm's pack-time parser (lib/manifest.js in the control plane) by running it on the example, and against the player's parser (internal/manifest).