7. Manifests — your own data, no code

What a manifest installs, as staff see it in Desk: a workspace, the bins on their aisles, and what is in each bin (sample S2).
A manifest is a file of data, not code. It adds your own record types, fields, screens, approvals or print formats to a customer's iVendNext. This chapter is for a developer whose app needs its own data inside the shop.
Reference implementation: Shipping your own data in a manifest · Approvals by workflow · the checker and installer ../tools/manifest/ · ../samples/s11_approvals/manifest.json (the permission example: copy its rows) · ../samples/s2_bins/manifest.json (five record types, a field on Item, a print format, a workspace, webhooks) · ../samples/s4_receipt_pack/ (a print format alone).
When to use it
Rung 4 of the ladder (chapter 3): configuration cannot hold your data and your app needs it inside the shop, so staff see it in Desk, a till key reads it or a webhook reports on it. A manifest is data. It runs nothing; your logic stays on your server (chapter 5) or in a till key (chapter 6). Every record type it declares gets the native Desk screens (chapter 13a).
Steps
-
Choose your prefix: one to three upper-case words (
PP BLN) and the matching app name (pp_bln). Every name you create starts with it. A field you add to one of the shop's own record types starts with the lower-case form (pp_bln_default_bin). Names on a site are shared and carry no owner, so the prefix is how an uninstall finds what is yours. (The samples'PP …prefixes are placeholders; there is no shared registry of prefixes yet, see the end of this chapter.) -
Write
manifest.json. Set"manifest_version": 2; an earlier manifest still installs once every one of its permission rows writes all nine rights (step 3). The kinds it may carry:- record data: record types (never submittable; child tables allowed), fields, display tweaks, print formats, notifications, workspaces, sale details, webhooks;
- screens: Report Builder reports, number cards, charts, dashboards, list settings, Connections links (your records listed under a shop record);
- approvals and numbering: workflows and numbering rules, on your own record types only;
- wiring: your event-feed registration and your remote till key, each installed switched off; the shop's administrator chooses the user and ticks them on;
- extension points (below): your services' subscriptions, Desk actions, save rules and a fiscal reader, each installed switched off.
The declarations of extension points that have not shipped (scan sources, receipt blocks, label layouts) are each refused, naming the extension point they wait on (chapter 9). Sandboxed modules are refused too (below).
-
Write every right out: all nine (
read, write, create, delete, report, export, share, print, email) on every permission row. A right you leave out is on: measured, a row written as read only came back with all nine rights on, because iVendFramework switches the rights a row does not mention on by default. So the checker refuses a row that leaves a right out, and a row that writes a right as anything but 0 or 1.samples/s11_approvals/manifest.jsonshows the form; the other sample manifests write all nine too. -
Check it yourself, before anyone installs it:
python3 tools/manifest/validate.py samples/s2_bins/manifest.json # VALID, exit 0Every refusal names the element and the reason, for example "REFUSED webhooks[0] (PP BLN Hook): a webhook may only call an address the manifest lists in allowed_urls".
-
Install. Today we run the installer for you on the shop's server; it has no screen or command of its own yet. A manifest from an outside partner is also reviewed by a person before it is installed. The installer checks the manifest again, refuses names someone else holds, and records each element as it creates it. If anything fails half-way it removes what it made before it reports. Each webhook gets a fresh secret, handed to you once; a secret never goes in the manifest.
-
Use your records over the record API, like any other type (chapter 5).
-
Export before you uninstall. The installer refuses to uninstall without an export of every record of your types (child rows and your added field values included), unless the administrator says explicitly that none is wanted. Uninstall leaves nothing behind: it drops your tables and the columns your fields added (the shop's own delete keeps them), and removes what the shop did to your types after install. A sale detail that booked sales already use is kept, switched off, because a booked sale keeps its detail; a reinstall switches it on again. If any element cannot be removed, the uninstall says so, and running it again finishes the job. One exception: attachments, versions and comments on your records stay after an uninstall; the export holds the records themselves.
-
A reinstall is clean: exactly the first install's schema, and none of the old data.
The extension points' kinds
Each record goes through the product's own checks when it is installed; a refusal there comes back in the product's words, and nothing is left. Each is installed switched off. The full contract of each extension point is in the extension-point library.
extension_subscriptions— your service for one extension point:key(your name for it),point(desk_actionorpage),label(1–40 characters),endpoint_url(https, listed inallowed_urls),timeout_seconds(at most 10), and for a page itspage_…settings andpage_roles.- Refused: switched on, consent given or stamped, a secret, the consent text, the stores it serves (
scope, the shop's choice), a sandboxed module. - The installer makes each secret and hands it to you once, named
<app>:sub:<key>. - The shop then reads what the service is sent, ticks the consent and switches it on.
- Refused: switched on, consent given or stamped, a secret, the consent text, the stores it serves (
desk_actions— a button on a Desk record: the record type, label, roles, when it shows (on_draft,states,conditions), itsinputsandsend_fields, andservice: thekeyof one of yourdesk_actionsubscriptions in the same manifest (the installer makes the subscription first).- Refused: switched on; a service the manifest does not install; a role open to visitors or one the system hands out by itself.
- The shop then switches the button on; it runs once its service is consented and on.
save_rules— a rule checked on every save of a record type, the shop's own (an Item, a Customer, a sale) or yours:rule_name(starts with your prefix) and the rule's settings.- Refused: switched on; a
bypass_rolesrole open to everyone; a message carrying markup (the product shows it as the refusal). - On a sale the product lets a rule only flag, never refuse: a sale the customer paid for is never stopped.
- The shop then switches each rule on.
- Refused: switched on; a
fiscal_readers— one per site: your outside service's result on the sale (the fiscal reader, chapter 11).- What you declare. You add the result fields to POS Invoice in
custom_fields, No Copy, allow-on-submit, at their own permission level (permlevel, the only place a manifest sets it), and name them; the map from your values to CLEARED, REPORTED or PENDING;writer_role(with your prefix) andwriter_level(that level);writer_users(short keys). - What the installer makes, in this order: the fields, the role, its one permission row (read and write at its level, nothing at level 0), one integration user per key, then the reader, switched off, serving the shop's own company. Nothing it makes reaches a sale yet, and no key leaves the install.
- The integration user is a desk user, so it takes one of the shop's licences. It holds your writer role and nothing else: if the site gives it any other role (an engine before 2026-09-30 gave every desk user Workspace Manager), the install is refused and removed. It has no API key until the shop makes one.
- Refused: switched on, naming companies, any source but the outside service. Warned, not refused: level 1 (the shop's Accounts Manager already writes POS Invoice there), a field without No Copy or allow-on-submit.
- Before install the shop turns on Clearance required for its country (the product refuses the reader otherwise). The reader serves every company on a site, so keep to one company per site.
- After install the shop:
- gives the role read, write and submit at level 0 (your service reads and saves a submitted sale; with them it can change any level-0 field a submitted sale allows, and submit a draft);
- generates the integration user's API key (User → Generate Keys) and hands it to you, as for any connected app (the integration user recipe);
- switches the reader on.
- At uninstall the role goes, and with it every grant naming it: the shop's own level-0 row for it on POS Invoice, and the role on any user or role profile it was given to. The product checks the writer rule then, and on every read: fields above level 0, No Copy, no other role writing their level, the role held by your users only. The installer reports whether it holds.
- What you declare. You add the result fields to POS Invoice in
- Sandboxed modules (
extension_modules) are refused: no extension point on this product runs one yet. When one does, a module carries our intake certificate, the same build is admitted once only, and an uninstall withdraws it, so its record stays. - Export: the uninstall's export also holds these records as the shop left them (switched on, consented, edited), never a secret. A reinstall gives back your manifest's own copy, switched off.
Worked example: an approval

A requester asks for a new price, a manager approves it, and the partner's outside service applies it once; the shop's price row then reads the approved price (sample S11).
A request to change a price moves Requested → Approved → Applied on your own record type, by a native workflow: four states, four transitions, each allowed to one role, no transition condition, self-approval off (the installer writes it off; iVendFramework's own default is on), change tracking on. The workflow never submits, cancels or changes a price itself. The roles must exist in the shop before install: a manifest refuses a role the shop lacks. The rows to copy: the requester, the approver and the applier read and write (the requester also creates); System Manager reads, reports, exports and prints only. A state's "who may edit" is a screen rule: over the record API anyone with write can change a request in any state.
An outside service (chapter 5) applies an Approved request through the record API, once. Its key can write only that request type and price rows; a User Permission on Price List, applicable to Item Price only, fences it to its own lists.
The safeguards that matter are in the recipe. The shop does not stop an edit from anyone with write. So the service reads the request's change history and refuses to apply a request whose price changed at any time after it was raised, one approved by the person recorded as raising it, and one with ten or more recorded changes (the shop shows only the latest ten; measured). The approver can then Reject it from Approved. Two people per change holds only while no one holds both the requester's and the approver's role, and only for prices this service writes: any other role with write on price rows skips the approval.
What is refused, and why
| Refused | Why |
|---|---|
| a server script, a client script, a web page, a web template | partner code in the shop's saves, in every user's browser, or on its public address |
| a submittable record type | it would join the shop's accounts and stock |
| a field you add to a shop record type that is mandatory, read-only, hidden, unique, defaulted or length-limited; any change to the shop's own fields | the server enforces these on every save, a till sale included |
| a webhook condition, or a notification, on a shop's own record type | evaluated inside the shop's save: one mistake would stop every sale |
| a webhook to an address you did not list, or a secret in the manifest | a webhook calls only addresses you declared |
| a custom workspace block, script in markup, a script print format, an HTML or button field, a display condition that runs code | it runs in the browser of everyone who opens the page, administrators included |
| a required sale detail | it would stop every sale on every till that does not fill it |
| a workflow transition condition, a state that submits or cancels, numbering the shop's own records (a sale's number included) | code on the server, or fiscal ground |
| a single-record or tree record type, or a field on a shop's single-record type | the installer makes and removes ordinary record types only |
| any name without your prefix; records open to visitors who are not signed in, or to a role the system hands out by itself (All, Desk User, Administrator) — and a notification sent to Guest or to one of those roles | names carry no owner; a manifest's records are for the shop's own users, and a row for All would reach every signed-in user (a shop's website customers included), and one for Desk User every staff user, whatever roles the shop's administrator chose; a notification to one reaches whoever holds a stored row for it, refused so every place a role is named follows one rule |
| a permission row that leaves a right out, or writes one as anything but 0 or 1 | a right left out is switched on, so every row says all nine |
| setting a till profile's receipt format | that step is configuration (chapter 4) |
Nothing leaves the shop that the shop did not grant. A webhook sends only its fixed thin body (record type, name, event, time) and you read the record with your own key. A print format loads nothing from outside the shop. A notification goes to the shop's own people and shows its own record's fields. The checker refuses the other channels. Its refusal is tested; the installer's change to always send the thin body has not yet been run on a test shop.
When your service is down, and when the till is offline
A manifest runs no code, and the rules above keep it out of the shop's own saves, so it cannot stop a sale. Its webhooks behave as every webhook does: three tries, then your reconciliation (chapter 5). A till key that reads your records works only online; the till dims it offline (per the till's own documentation; not run here).
Technical notes
Sources for the plain layer
- Fiscal reader: the fiscal reader is the extension point P-FISCAL.
- Sandboxed modules: the first extension point to run one is planned as P-CHECK, Wave B.
- Nothing leaves the shop: the checker's refusal of the other channels is proven by its own tests; the installer's change to always send the thin body has not yet been run on a test shop.
- Offline: a till key that reads your records is dimmed offline per the till's own documentation; not run here.
What the kit has proven
- Install → export → uninstall → reinstall leaves the schema as found, for the earlier kinds and for a manifest carrying every new kind except the remote till key. Planted leaks (a report, a link, a workflow state) were each seen by the check.
- Every refusal of the earlier kinds has its own test, each refused with its own reason and nothing else (the extension points' kinds: each refusal the guide names); the installer's site refusals (a name taken, a missing role or column) made nothing.
- The extension points' kinds (2026-10-07): a Desk action, a Desk-action and a page subscription, and two save rules installed switched off. When the shop switched them on, the product accepted them, the consent was stamped and a rule bit. The export kept the shop's choices and the uninstall left the schema as found. A fiscal reader was refused at install when the shop's country did not wait for clearance, and the product refused to switch it on at level 1, without No Copy, and with its role held by someone else. Installed in order, it switched on, and a service's Queued, unknown and Accepted results showed on the sale as pending, unchanged and cleared.
- The new kinds were used, not just installed: the numbering rule numbered a claim, the workflow moved it through its states, the report listed the open claim and its totals row, the card summed, the chart grouped, the Connections link listed the claims, the feed registration wrote nothing while off and rows once switched on.
- The approvals sample: a requester cannot approve, nor set the state by hand; the applier's role cannot approve; a person holding both roles cannot take the Approve action on their own request, and one who writes the state in directly is refused by the service; approving changes no price; the service applies once and a second run changes nothing; a crash between the price and the action is finished without rewriting the price; every right on every row of the record type equals the manifest's; uninstall exports the requests first.
What it has not
- The remote till key installs switched off and was read back on the test shop (2026-10-07). The manifest-installed key was not pressed on a till: the press through the till's own endpoint that was answered used a key record the bench wrote directly (row BUMP, G5b, run
pp-20261007T014940Z-64695). A press from a physical till was not tried. - Extension points: a fiscal reader on a site whose POS Invoice has no custom permission rows yet (the framework then copies the standard rows in once, and they stay after an uninstall: POS Invoice keeps the rights it had at install, and a later release that changes its standard rights does not reach the site until the shop resets them); installing again within seconds of uninstalling the same integration user (it can fail on a background job of the deleted user, and is removed cleanly; installing again works); a desk user's write to the result fields being discarded (spec §10 S4); a site with more than one company (the reader serves every company on the site, so the product refuses it unless each one's country waits for clearance; our rule is one company per site).
- The markup check is a tripwire, not a guarantee (a print template's output is not escaped). A manifest from an outside partner is still reviewed by a person before it is installed.
- There is no installer screen yet, and no shared registry of reserved names.
- Uninstall leaves attachments, versions and comments on your records; the export holds the records themselves.
- A shop with developer mode off was not tried; the test shop runs with it on.
- Approvals: two services at once, many requests at once, approvals across companies, a price row that does not exist yet, an approver who is also the applier, and a requester raising a request in Desk were not tried. Approvals across stores are S14. Measured on 2026-10-08: the shop returns only the latest ten versions of a request, and a request with ten or more is not applied; the nightly export ran by its cron line.
The pictures
- S2 (under the title): walked on our test bench on 2026-10-07, iVendNext POS app at
8dc36aab8; frame Desk, 1440 wide. The names are the sample's own placeholders. Its till key "Where is it?" is not shown: it is installed switched off. - S11 (the worked example): walked on our test bench on 2026-10-07, iVendNext POS app at
8dc36aab8; frame Desk, 1440 wide. Each step was taken by that person's own key (S11's bench driver); that a requester cannot approve is shown by S11's tests, not by the pictures.
See also
3. The ladder and the four rules · 4. Configure first · 5. Outside apps and the event feed · 9. The extension-point catalogue and requests · 13a. Screens and user experience