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 |
Effective admin credentials | username, password. |
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" },
"login": { "username": "admin", "password": "admin" },
"steps": [
{ "step": "installNextcloud", "adminUser": "admin", "adminPass": "admin", "adminEmail": "admin@example.com" },
{ "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.
installNextcloud¶
Installs Nextcloud against SQLite. In practice this is an idempotent marker: the real install runs once during boot and is skipped if the persisted DB already matches the current bundle.
{ "step": "installNextcloud", "adminUser": "admin", "adminPass": "admin", "adminEmail": "admin@example.com" }
Maps to:
occ maintenance:install --database sqlite --database-name nextcloud --admin-user admin --admin-pass admin --admin-email admin@example.com --data-dir <data>,
followed by the WASM-safe config flags.
login¶
Authenticates a user via a web request to /index.php/login, establishing a
session cookie (captured by the runtime cookie jar). This is not an occ
command.
{ "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).
createGroup¶
{ "step": "createGroup", "gid": "teachers" }
Maps to occ group:add <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 <appId>. The app must be present in the trimmed bundle.
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 a user's files and indexes it.
{ "step": "writeFile", "path": "/admin/files/welcome.md", "contents": "# Welcome" }
Writes the bytes into the user's data directory, then runs occ files:scan <uid>
so Nextcloud registers the file. contents can also be supplied from a resource
(URL / base64) rather than inline literal text.
createShare¶
Creates a share for an existing path.
{ "step": "createShare", "path": "/welcome.md", "shareType": "public" }
{ "step": "createShare", "path": "/Reports", "shareType": "group", "shareWith": "teachers", "permissions": "read" }
shareType is typically public (link), user, or group. For user/group
shares set shareWith; permissions defaults to read.
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
writeFile¶
Writes a file into the instance. path is resolved against the Nextcloud root
(/www/nextcloud) unless it starts with /. The contents come from content
(UTF-8 text, or base64 when "encoding": "base64") or are fetched from url
(handy for binary payloads too large to inline — the host must allow CORS).
Parent directories are created as needed.
{ "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). To make a file show up in a user's Files view,
write it under that user's data dir and follow with runOcc files:scan <user>.
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.