Skip to content

Blueprint authoring

A blueprint is one JSON file that packages an application out of platform components: its tables, the automation around them, the screens people use, and the data it ships with. Install the file and the platform builds every piece in one pass. This guide covers how the file is structured, the order you author it in, and how to validate and install it. It uses a small demo application to show the shape; for the components a blueprint assembles, read Platform first.

Goal

Author a blueprint that installs cleanly: correct structure, valid references, and data that matches its own choices.

Prerequisites

  • platform.tables.write to create tables, plus the write capability for each surface the blueprint declares (platform.rules.write, platform.workflows.write, platform.approvals.write, and so on).
  • A JSON editor. A blueprint is hand-authored or generated, then installed through install_app_blueprint. Connected over MCP as a Builder, you can also assemble the same application one object at a time through each page’s own tool (create_table, create_role, and so on), trying each piece before moving to the next, instead of authoring the file directly.

The file at a glance

A blueprint declares these top-level keys. Empty arrays are fine for sections you do not use.

{
"name": "Acme Requests",
"description": "A simple request application.",
"category": "custom",
"$schema": "https://flowlakes.com/blueprint/v2.0",
"schema_version": "2.0.0",
"system_tables": ["fl_tasks", "users", "groups"],
"metadata": {
"name": "Acme Requests",
"slug": "acme-requests",
"description": "A simple request application.",
"category": "custom",
"version": "1.0.0",
"tags": ["requests"],
"author": "Acme",
"organization": "Acme",
"license": "proprietary",
"is_public": false,
"visibility": "private",
"readme": "A demo application that captures and tracks requests."
},
"choices": [],
"tables": [],
"relationships": [],
"rules": [],
"workflows": [],
"notifications": [],
"approvals": [],
"dashboards": [],
"navigation": { "modules": [] },
"roles": [],
"forms": [],
"service_hub_items": [],
"seed_data": {},
"demo_data": {}
}

name, description, and category appear both at the top level and inside metadata; keep them identical. The system_tables array lists the real platform tables your blueprint reaches (for example fl_tasks, users, groups). Reference those directly. Do not fabricate a parallel copy of a platform table.

Steps

1. Author the data layer

Define choices first, because tables reference them. Each choice value carries a value, a label, and a sort_order; lifecycle status values also carry a color and their allowed_transitions. A field points at a choice set instead of inlining its own options.

Then define tables. Classify each table honestly:

  • Reference or configuration tables. Lookup data an admin maintains (priorities, categories). These get seed data.
  • Process tables. The work items people act on (a request, a task). A process table sets task_enabled: true and a unique number_prefix. That is what gives it a record number, an activity timeline, and SLA behavior.

A process table’s lifecycle is its inherited state column, never a second field the draft invents. Declare the table’s own lifecycle values as choices for field name state (with allowed_transitions if the table needs a state machine), and set default_state to one of those same values. An approval policy’s outcome (on_approved_state, on_rejected_state) writes state directly, so its value must be one of the table’s declared state choices too. Do not add a second status field alongside state: a policy that writes state while an action rule watches a different field never fires, and the record ends up with two lifecycles that disagree.

A process table also declares the number field the prefix fills. Validation refuses a task_enabled table that carries a number_prefix and no number field, because the platform generates the number at insert time and has nowhere to write it:

{
"field_name": "number",
"field_type": "string",
"field_length": 50,
"is_indexed": true,
"ui_config": { "readonly": true }
}

A process table can inherit shared behavior by naming fl_tasks as its parent, so common fields and task automation come for free. See Tables and records.

Add relationships for reference fields between tables. A field that points at a person uses a reference to users; a field that points at a team references groups.

2. Author the logic layer

Add rules, workflows, and approvals.

  • Rules are trigger-condition-action logic on one table. Give each rule an explicit execution order so calculation runs before validation runs before action. Rule conditions and workflow branch conditions must use supported operators only. An unsupported operator is rejected at validation time, which is deliberate: at runtime an unknown operator would silently mis-fire. See Operators.
  • Workflows run multi-step processes asynchronously off a trigger. See Workflows.
  • Approvals are declared as approval policies that self-trigger on their own target tables and conditions. Do not build a custom approval table. Declare trigger_on_create, trigger_on_update and trigger_on_field_change explicitly: a condition whose value is reached by an UPDATE (not supplied at create) needs trigger_on_update: true or that field named in trigger_on_field_change, or the policy never routes. See Approvals, Triggers.

Script bodies inside a rule or a workflow step follow The scripting sandbox.

3. Author the presentation layer

Add dashboards, notifications, navigation, and roles.

  • Dashboards lay out on a 10-column grid where every row sums to exactly 10. KPI cards are height 2; charts, tables, and queues are height 4. Fill the grid with no trailing gaps.
  • Notifications cover the lifecycle events that matter, with {{field_name}} placeholders in the subject and body. See Notifications. A notification’s table_name must name a table this package actually ships or that the tenant already has; validation refuses one that names no real table, rather than installing a rule that silently never fires.
  • Navigation groups the app into modules; the first item in a module is its dashboard. Icons belong on navigation modules and menu items.
  • Roles grant real platform capabilities and per-table permissions. Declare at least an admin, a manager, and a user role. See Identity and access.

4. Author the data the blueprint ships with

Two data sections, with different lifetimes:

  • seed_data. Always installed. Put reference and configuration rows here (the priorities, categories, and calendars the app needs to function). Seed values must match the choice values you defined exactly. Never seed process records here.
  • demo_data. Opt-in. Put sample process records and their supporting demo groups, services, and configuration items here. An installer chooses whether to include it with the Include demo data checkbox in Browse Apps. Inside demo_data you can reference the installing user with $current_user, and cross-reference a row you are seeding with {"$ref": "alias"} after declaring that alias with _ref. Rows seed order-independently, so never rely on key or table order.

5. Validate, then install

Install a complete file directly with install_app_blueprint, which validates the whole file through the same validator before installing anything.

Validation checks that the JSON is well-formed, every choice-set reference resolves, every reference-table value exists, and each task-enabled table has a unique number prefix.

6. Update an installed application

Each component the platform installs is found again, on every later update, by its own stable key, not by its display name: a table by its table_name, a workflow by its name, and so on (the full list is in Blueprint standard, Keys). Renaming a component’s display copy, such as rewording a workflow’s name, is not a rename to the platform unless you say so: declare the component’s own "key" once, and its display fields are then free to change on every later release, or declare "renamed_from": ["Old name"] on the renamed component so the installer updates the tenant’s existing row instead of leaving it behind and creating a second one. Dropping a component from the package retires it everywhere this tenant has it installed, by key.

Editing the file changes what NEW installs receive. A tenant that already installed the app receives nothing until someone applies the update from Marketplace, so bump metadata.version and apply the update in every tenant that should get the change.

Clicking Update shows what the update would do before you confirm it:

  • Summary. The components the update would add or change, or “No changes detected”.
  • Breaking changes. Anything that alters an existing table or field. An update with breaking changes is flagged rather than blocked.
  • Installed record. Where the platform’s record of what this tenant installed comes from. “Recorded for this app” gives an exact list of changes. “Not recorded” means the changes cannot be computed, and the dialog offers to re-apply everything the blueprint declares instead. That re-apply updates existing components in place and records a baseline, so the next update is exact.

After you confirm, the result lists what was applied and what was not. An update that leaves anything unapplied reports a failure and does not update the record of what is installed, so applying again retries exactly what failed.

Uninstalling reports the same way. Each removal step runs independently, and any step that leaves something behind is named in the result. The application is not fully removed until that list is empty.

Building one object at a time over MCP

An AI client connected over MCP as a Builder does not have to author the whole file at once. It works the way an admin does: read what exists (list_tables, get_schema, and the other list_*/get_* tools), build one object at a time through that object’s own tool (create_table, create_role, create_business_rule, create_workflow, create_approval_policy, create_notification_rule, create_dashboard, create_menu_item, create_catalog_item, and so on), and try each piece before moving to the next. Every write runs through the same validator install_app_blueprint runs, so a Builder never ends up with something the installer would have refused.

Verification

You know it worked when:

  • After install, the app’s tables, navigation module, and dashboards appear in the tenant.
  • Creating a record in a process table assigns it a number and a starting state.
  • If you installed with demo data, the sample records are present; without it, only the reference data is.

Troubleshooting

SymptomCauseFix
Validation rejects an operatorA condition uses an unsupported operatorUse only the supported operators
Install fails on a duplicate prefixTwo task-enabled tables share a number_prefixGive each process table a unique prefix
Validation rejects a task-enabled tableThe table has a number_prefix and no number fieldDeclare the number field shown above
An update reports “No changes detected” after a file editThe tenant’s installed record is a version snapshot that no longer matches the fileBump metadata.version, or re-apply everything from the update dialog
An uninstalled app is still partly presentA removal step left something behindRead the named steps in the uninstall result and clear them before reinstalling
A choice-driven field is emptySeed value does not match a defined choice valueAlign seed data with the choices exactly
Demo records missing after installDemo data was not requestedRe-install with the Include demo data checkbox
Validation rejects a cancelled stateThe table has its own cancel_reason field, but the cancelled choice row has no requires entry for itAdd {"field": "cancel_reason"} to that state’s requires list
Validation rejects a string fieldIts description reads like an enumerated option list (one of:, options:, allowed values:)Declare it as a choice field with a matching choices[] entry instead of describing the options in prose
Validation rejects a top-level keyThe key is not one of the declared sections or the base-object keys (name, description, category, $schema, schema_version, metadata, depends_on)Remove it, or correct the spelling to the section the installer actually reads
  • Blueprint standard: the full specification this guide walks through, with every required section and valid enumeration.
  • Platform: the primitives a blueprint assembles.
  • The scripting sandbox: the environment for script bodies in rules and workflows.
  • Operators: the condition and filter vocabulary validation enforces.
  • Glossary: every platform term used above.