blueprint.json¶
A blueprint is a portable JSON description of the initial state the Nextcloud Playground should provision inside a browser scope. It is applied on boot, after Nextcloud is installed against SQLite.
- Default blueprint:
assets/blueprints/default.blueprint.json - Schema:
assets/blueprints/blueprint-schema.json - Normalization:
src/shared/blueprint.js - Applied by:
src/runtime/bootstrap.js
Provisioning steps run through Nextcloud's occ command (executed via the
shebang-free console.php wrapper described in
development.md), or through web requests for session-based
steps. Don't declare features that don't work under WebAssembly — see
KNOWN-ISSUES.
Loading a blueprint¶
The shell resolves a blueprint in this order
(resolveBlueprintForShell in src/shared/blueprint.js):
?blueprint=— inline base64 of the JSON (URL-safe alphabet, padding optional), or anhttp(s)URL.?blueprint-url=— a remote URL (primary form).?blueprint-data=— legacy alias for?blueprint=(deprecated).config.defaultBlueprintUrl→assets/blueprints/default.blueprint.json.- The built-in default.
http://localhost:8085/?blueprint=<base64-of-json>
http://localhost:8085/?blueprint-url=https://example.com/demo.blueprint.json
Top-level structure¶
| Property | Use | Notes |
|---|---|---|
$schema |
Schema reference | Optional but recommended. |
meta |
Descriptive metadata | title, author, description. |
debug.enabled |
Show PHP errors | Diagnostics only. |
browserCompatibility.sandboxedIframes |
Sandboxed app iframe handling | strict (default) or the explicit service-worker workaround described below. |
landingPage |
Entry route | Normalized to start with / (e.g. /index.php/apps/dashboard/). |
siteOptions |
Instance options | title, locale, timezone. |
login |
Legacy admin credentials | username, password. Folded into admin during normalization. |
admin |
Admin account for install / autologin | username, password, email. Preferred over login. |
steps |
Ordered provisioning steps | See the step reference below. |
{
"$schema": "./blueprint-schema.json",
"meta": {
"title": "Nextcloud demo",
"author": "team",
"description": "Baseline demo: a group, two users, an enabled app, a shared file."
},
"debug": { "enabled": false },
"landingPage": "/index.php/apps/dashboard/",
"siteOptions": { "title": "Nextcloud Playground", "locale": "en", "timezone": "UTC" },
"admin": { "username": "admin", "password": "admin", "email": "admin@example.com" },
"steps": [
{ "step": "installNextcloud" },
{ "step": "setConfig", "key": "default_phone_region", "value": "ES" },
{ "step": "createGroup", "gid": "teachers" },
{ "step": "createUser", "uid": "alice", "password": "alice-pass", "displayName": "Alice" },
{ "step": "addUserToGroup", "uid": "alice", "gid": "teachers" },
{ "step": "enableApp", "appId": "activity" },
{ "step": "disableApp", "appId": "firstrunwizard" },
{ "step": "writeFile", "path": "/admin/files/welcome.md", "contents": "# Welcome to the playground" },
{ "step": "createShare", "path": "/welcome.md", "shareType": "public" },
{ "step": "login", "username": "admin", "password": "admin" }
]
}
Sandboxed app iframe compatibility¶
The default browserCompatibility.sandboxedIframes mode is strict. Some
trusted apps load a same-origin iframe with both allow-same-origin and the
credentialless attribute. That ephemeral browser context cannot use
Playground's Service Worker, so its PHP routes and packaged assets fail to
load. Such blueprints can opt into the workaround:
{
"browserCompatibility": { "sandboxedIframes": "service-worker" }
}
In this mode Playground removes credentialless only from scoped app iframes
that already declare allow-same-origin. It does not grant that sandbox token.
Removing the ephemeral context reduces iframe isolation, so only enable the
mode for trusted app code.
Step reference¶
Steps run sequentially in array order. Each maps to an occ command or a web
request.
Field aliases accepted on every step (so both the documented names and the
runtime names work): uid/username/owner, gid/group, appId/app,
contents/content.
installNextcloud¶
Idempotent marker. The real install (occ maintenance:install against SQLite)
runs once in bootstrap before steps, using top-level admin. Declaring this
step is optional; it is a no-op when the instance is already installed (the
usual case) so documented examples do not warn.
{ "step": "installNextcloud" }
login¶
Establishes a logged-in session for username/password (or uid) by running
the same server-side session helper as playground autologin (UserSession::login
+ session token). The request goes through php.request() so the cookie jar
captures Set-Cookie. A form POST to /index.php/login is not used — the
login CSRF token is awkward to round-trip under wasm.
A successful login step replaces the default admin autologin, so a
blueprint can land as a non-admin user. Without this step, config.autologin
still signs in the admin.
{ "step": "login", "username": "admin", "password": "admin" }
createUser¶
{ "step": "createUser", "uid": "alice", "password": "alice-pass", "displayName": "Alice", "email": "alice@example.com" }
Maps to OC_PASS=<password> occ user:add --password-from-env --display-name "<displayName>" <uid>.
The password is passed via the OC_PASS env var (no interactive prompt is
possible). username is accepted as an alias for uid.
createGroup¶
{ "step": "createGroup", "gid": "teachers" }
Maps to occ group:add <gid>. group is accepted as an alias for gid.
addUserToGroup¶
{ "step": "addUserToGroup", "uid": "alice", "gid": "teachers" }
Maps to occ group:adduser <gid> <uid>. The user and group must already exist.
enableApp¶
{ "step": "enableApp", "appId": "activity" }
Maps to occ app:enable --force <appId>. The app must be present in the
trimmed bundle (or installed first with installApp). app is accepted as an
alias for appId.
disableApp¶
{ "step": "disableApp", "appId": "firstrunwizard" }
Maps to occ app:disable <appId>.
setConfig¶
Sets a config.php system key.
{ "step": "setConfig", "key": "default_phone_region", "value": "ES" }
{ "step": "setConfig", "key": "filelocking.enabled", "value": false, "type": "bool" }
{ "step": "setConfig", "key": "enabledPreviewProviders", "value": [], "type": "json" }
Maps to occ config:system:set <key> [<subkey>...] --value <value> [--type bool|integer|json].
Specify type for non-string values.
writeFile¶
Writes a file into the instance. Path resolution:
/<uid>/files/...(documented shortcut) → that user's data directory, thenocc files:scan <uid>so Files / shares can see it.- relative paths (
config/mimetypemapping.json,data/admin/files/sample.md) → the Nextcloud root (/www/nextcloud/...). A write underdata/<uid>/files/is also scanned. - already-absolute VFS paths (
/www/...,/persist/...) are used as-is.
Contents come from content / contents (UTF-8, or base64 when
"encoding": "base64") or are fetched from url (the host must allow CORS).
Parent directories are created as needed.
{ "step": "writeFile", "path": "/admin/files/welcome.md", "contents": "# Welcome" }
{ "step": "writeFile", "path": "config/mimetypemapping.json", "content": "{\"elpx\":[\"application/vnd.exelearning.elpx\",\"application/zip\"]}" }
{ "step": "writeFile", "path": "data/admin/files/sample.elpx", "url": "https://raw.githubusercontent.com/owner/repo/main/fixtures/sample.elpx" }
Useful for registering a custom MIME type (write config/mimetypemapping.json +
config/mimetypealiases.json, then runOcc maintenance:mimetype:update-js and
maintenance:mimetype:update-db).
createShare¶
Creates a share for an existing path in a user's files. Stock Nextcloud has
no occ command for this (so runOcc is not a workaround). The step
bootstraps Nextcloud and calls OCP\Share\IManager
— the same engine behind POST /ocs/v2.php/apps/files_sharing/api/v1/shares.
path is relative to the owner's files (/welcome.md). A writeFile-style
/<uid>/files/... or data/<uid>/files/... path is also accepted and implies
the owner. Default owner is admin (or an explicit owner / uid /
username). The file must already exist and be scanned (see writeFile).
{ "step": "createShare", "path": "/welcome.md", "shareType": "public" }
{ "step": "createShare", "path": "/Reports", "shareType": "group", "shareWith": "teachers", "permissions": "read" }
{ "step": "createShare", "path": "/welcome.md", "shareType": "user", "shareWith": "alice", "permissions": ["read", "update"] }
| Field | Use |
|---|---|
shareType |
public / link (3), user (0), group (1). Names or OCS integers. |
shareWith |
Required for user / group. |
permissions |
read (1), update (2), create (4), delete (8), share (16), all (31), a bitmask, or an array of names. Default: read for public links, all otherwise (same as the OCS API). |
password |
Optional public-link password. |
expireDate |
Optional YYYY-MM-DD public-link expiry. |
note, label |
Optional share note / label. |
installApp¶
Installs a Nextcloud app that is not part of the trimmed bundle by
downloading a ZIP archive, extracting it into the apps directory, and enabling
it. This is what lets an external app repository (e.g. nextcloud-exelearning)
ship a blueprint that boots the playground with its own app pre-installed.
{ "step": "installApp", "appId": "exelearning", "url": "https://github.com/exelearning/nextcloud-exelearning/releases/download/playground/exelearning.zip" }
urlmust point to a ZIP whose contents include the app'sappinfo/info.xml. A single common leading folder is stripped automatically, so both a built app ZIP (exelearning/appinfo/…) and a GitHub source archive (repo-branch/appinfo/…) work.- Files are written into
apps/<appId>inside the readonly core's (writable MEMFS) apps path, so noapps_pathschange is required. - After extraction the app is enabled with
occ app:enable --force <appId>(the--forcebypasses the Nextcloud version requirement). Set"enable": falseto extract without enabling.
The app must be a built artifact: source archives that rely on a compiled
js/ bundle won't render in the browser unless that bundle is included in the
ZIP.
The fetch happens cross-origin from the runtime worker, so the ZIP host must
send Access-Control-Allow-Origin. raw.githubusercontent.com and GitHub Pages
do; GitHub release-asset downloads do not (they redirect to Azure Blob
without CORS headers). To serve a release asset, route it through a CORS proxy,
e.g. the shared github-proxy worker:
https://github-proxy.exelearning.dev/?repo=<owner/repo>&release=<tag>&asset=<file>.zip
unzip¶
Fetches a ZIP or gzip-tar and extracts it into destination (relative to the
Nextcloud root, or absolute), stripping a single top-level wrapper folder.
Use this to overlay a standalone bundle (for example a static editor) into an
already-installed app.
{ "step": "unzip", "url": "https://example.com/editor.zip", "destination": "apps/exelearning/js/editor" }
The ZIP host must send Access-Control-Allow-Origin (same CORS rule as
installApp).
runOcc¶
Escape hatch for any occ command without a dedicated step.
{ "step": "runOcc", "args": ["maintenance:mode", "--off"] }
Maps to occ <args...>. Use for one-off commands; prefer the typed steps above
where they exist.
occ execution notes¶
- Every occ step runs through the shebang-free
console.phpwrapper with$_SERVER['REQUEST_URI']unset so patchedbase.phptreats it as CLI. - There is no shell — argv is passed as an array, never string-concatenated.
- Secrets go through env (
OC_PASS), not argv where avoidable. - A non-zero exit code surfaces the occ stdout/stderr for diagnosis.
debug.enabled¶
When true, PHP display_errors is on and error_reporting is E_ALL — useful
when a step fails. Leave it off for normal runs.
Validating a blueprint¶
make test # exercises blueprint normalization
make lint
When you add or change a step type, update assets/blueprints/blueprint-schema.json,
the step handler, this document, and add a unit test under tests/*.test.mjs.