Integrator guide — authoring a virtual app by hand
This guide walks you through creating a new virtual app in OpenBuilt without using the visual editor (which lives in chain spec openbuilt-page-editor — not yet shipped). At this stage, OpenBuilt is integrator-only: you write JSON and the runtime renders it.
What you author
A virtual app is one record in OpenBuilt's Application OR schema. The shape is:
{
"slug": "permit-tracker", // kebab-case, 2–48 chars
"name": "Permit Tracker",
"description": "Track building permits through review stages.",
"version": "0.1.0",
"status": "draft", // draft → published → archived
"manifest": {
"version": "1.0.0",
"dependencies": ["openregister"],
"menu": [ ... ],
"pages": [ ... ]
}
}
The manifest object validates against @conduction/nextcloud-vue/src/schemas/app-manifest.schema.json. The closed type enum for pages is index | detail | dashboard | logs | settings | chat | files | form | custom.
Creating a virtual app with the wizard
For most operators the visual wizard at Virtual apps → New application is the quicker path. It walks you through three steps in one round-trip:
- Identity — pick a slug + human-readable name + description.
- Versions — accept the default chain (
development → staging → production) or adjust it. Each version maps to its own per-version register (openbuilt-{slug}-{versionSlug}) so production data is physically isolated from staging and development. The wizard's chain editor enforces ADR-002's linear-chain rule (no fan-out, no cycles, exactly one terminalproductiontier). - Permissions — pick the owners / editors / viewers. The caller is
pre-filled into
owners. Groupgroup:*becomes the "all signed-in users" wildcard per REQ-OBRBAC-004.
On submit the wizard atomically creates the Application record, the N
ApplicationVersion rows, and N per-version registers — and seeds each
register with the default schema set (hello-message by default) under the
namespaced slug {appSlug}-{versionSlug}-{originalSchemaSlug}. The manifest's
config.register and config.schema pointers are rewritten to match
(openbuilt-{slug}-{tier} and {appSlug}-{tier}-hello-message respectively)
so the insights service and the runtime each address the right per-tier slice.
Empty-state landing — fresh installs no longer auto-seed a hello-world
Application (the legacy SeedHelloWorld repair step was retired by
openbuilt-versioning-model). New deploys land the admin on an empty Virtual
apps index with a CTA pointing at the wizard. Pre-existing installs are not
affected; the migration step MigrateToVersionedModel only fires when
pre-spec-C Application rows are present and is idempotent on re-runs.
For further reading on what each step writes through to OR, see
openbuilt-runtime.md and the wizard chain spec
openspec/changes/openbuilt-app-creation-wizard/.
Step-by-step (manual / integrator path)
- Pick a slug. Must be kebab-case, 2–48 chars, unique within your organisation. The synthetic appId in CnAppRoot becomes
openbuilt-${slug}. - Design your schemas in OpenRegister directly (the OpenBuilt schema editor lands in chain spec
openbuilt-schema-editor). At minimum: one schema per primary entity your app shows. - Author the manifest as JSON. The canonical example is the seeded
hello-worldApplication — open it in OpenBuilt's manifest editor (top-bar OpenBuilt entry → Virtual apps → hello-world) and read its manifest. - Save as
draftwhile iterating. The textarea editor validates each save against the canonical schema; you see the failing JSON path on save error. - Transition to
publishedwhen ready (via OR's lifecycle endpoint or the editor's Publish action — landing in chain specopenbuilt-versioning). On publish, OpenBuilt's lifecycle creates the correspondingBuiltAppRouteso/builder/{slug}becomes reachable.
Manifest checklist
Per ADR-024:
version(semver) — your app's content versiondependencies— list of NC app IDs that must be installed (almost always["openregister"])menu[]— at least one entry; supports one level ofchildren[]pages[]— at least one entry; every page'sidMUST be unique and match a vue-router route namelabel/titlestrings are i18n KEYS, not literals. The consuming app'st()resolves them. Use kebab.dot.notation:myapp.permits.title.list.
Per ADR-007:
- Every translation key MUST exist in
l10n/en.jsonANDl10n/nl.jsonof the OpenBuilt repo (until per-virtual-app translations land in chain specopenbuilt-page-editor).
Reading the seed manifest
The seeded hello-world Application is the canonical reference. Its manifest exercises:
- index page → drives
CnIndexPagewithregister: openbuilt,schema: hello-message, three columns - detail page → drives
CnDetailPagekeyed on:id - form page → drives
CnFormPagewithmode: createandsubmitEndpointgoing to OR's REST
See lib/Repair/SeedHelloWorld.php buildHelloWorldManifest() for the full JSON.
When you hit a limit
The closed type enum can't be extended from a manifest — adding a new page type requires a library-level openspec change in @conduction/nextcloud-vue. If you need something the four built-in types can't express:
- Confirm the requirement isn't satisfied by
form(the most flexible built-in). - Open an issue on
ConductionNL/nextcloud-vuedescribing the new page type's shape. - As an interim, mount a custom Vue component via
type: "custom"+component: "MyCustomPage"and register the component in OpenBuilt'scustomComponentsmap. (Note: spec #1 only ships the built-in types — thecustomComponentsregistry surface lands when a real consumer needs it.)
What does NOT work yet (spec #1 limitations)
- No visual editor — JSON textarea only. Visual editor: chain spec
openbuilt-page-editor. - No schema designer — schemas must be authored in
lib/Settings/openbuilt_register.jsonand imported via the repair step. Runtime schema authoring: chain specopenregister-runtime-schema-api. - No draft preview — only
publishedapps appear at/builder/{slug}. Draft preview: chain specopenbuilt-versioning. - No per-app permissions — auth-only visibility for v1; everyone in your organisation sees every virtual app. Per-app RBAC: chain spec
openbuilt-rbac. - No export to a real Nextcloud app — virtual-only. Export pipeline: chain spec
openbuilt-export-to-real-app.
If any of these limitations block your project, talk to Conduction — chain spec prioritisation can shift.