lumio deploy
Upload a built extension bundle to Lumio as a new version. The version starts in draft status and must go through the submission wizard to enter the review process.
Usage
lumio deploy [-v <version>] [-d <description>] [--breaking] [--draft] [--token <token>]
Example
# Build first, then deploy
lumio build
lumio deploy -v 1.2.0 -d "Added live score updates from ESPN"
Or let it read the version from lumio.config.json automatically:
lumio deploy -d "Fixed score display alignment"
Options
| Flag | Alias | Description |
|---|---|---|
--version <semver> | -v | Version string (default: reads from lumio.config.json) |
--description <text> | -d | Override the uploaded release notes |
--breaking | - | Flag this version as a breaking change (disables auto-update for existing installs) |
--draft | - | Upload as draft without triggering the submission flow (default behavior - this flag is explicit) |
--token <token> | - | API token for authentication (skips interactive login). Also reads from LUMIO_TOKEN env var |
--outdir <path> | - | Location of the built bundle (default: dist) |
Pre-flight checks
Before uploading, the CLI runs these validation checks:
- Sound file format - each file in
dist/sounds/is validated against the supported audio formats (MP3, WAV, OGG, WebM, M4A, AAC, FLAC) and checked for existence - Sound count - the number of sound files is checked against the resolved
max_soundslimit (fetched from the API viaGET /v1/developer/extensions/{id}/limits) - Per-file size - each sound file is checked against the resolved
max_sound_file_sizelimit - Total storage - the combined size of all sound files is checked against the resolved
max_sound_storage_byteslimit - Manifest validation -
lumio.config.jsonis validated against the extension schema; a--versionoverride is written into the uploaded manifest so its version matches the request - Data schema - when the project has
server/schema.ts(orserver/schema.js/server/schema.mjs), it is bundled and its default export must be adefineSchema(...)result; a schema file that does not build or exports something else stops the deploy - Functions manifest - when an extension with
"server": true(not an automation node) hasserver/functions.ts,dist/functions.jsonmust exist; runlumio buildfirst - Changelog entry - the release notes come from the
## [x.y.z] - YYYY-MM-DDsection ofCHANGELOG.mdfor the deployed version, unless-doverrides the uploaded text. A missing changelog is allowed. If one exists but has no matching version entry, a local deploy requires-d; CI can upload without notes
If any check fails, the deploy is blocked with a descriptive error message.
What happens on deploy
- CLI reads
lumio.config.jsonand validates the extension - All built files from
dist/are collected (JS, CSS, HTML, images, fonts, video, audio, JSON, Lottie, source maps) - Sound files placed in
dist/sounds/are registered as extension-bundled sounds - they appear in the account's sound library when the extension is installed - Each file is uploaded individually as a multipart
filesfield, named by its/-separated path relative todist/(also on Windows) (includingfunctions.json, the functions manifest) alongsidesource.tar.gz, manifest, release notes and, when the project has aserver/schema.ts, thedata_schemafield (thedefineSchemaresult as JSON). Lumio checksfunctions.jsonagainst that data schema (format version, names, filter operators, fixed row ids, and every table must be declared); a failed check rejects the upload with400and the reason, e.g.functions.json: `getRules` uses table `missing`, which the version's data schema does not declare - A new
ExtensionVersionrecord is created with statusdraft - The server derives each bundle and source storage key from the extension ID and validated manifest version; the client does not supply storage keys
- Each file gets a row in
extension_version_fileswith content-type, size, and content hash
Supported file types
| Category | Extensions |
|---|---|
| Code | .js, .css, .html, .map |
| Images | .png, .jpg, .jpeg, .gif, .svg, .webp, .avif, .ico |
| Video/Audio | .mp4, .webm, .ogg, .mp3, .wav |
| Fonts | .woff, .woff2, .ttf, .otf, .eot |
| Data | .json, .lottie |
Every served bundle file uses X-Content-Type-Options: nosniff. HTML, SVG, JSON, source maps, Lottie, and other content outside JavaScript, CSS, raster images, fonts, audio, and video also use Content-Security-Policy: sandbox and Content-Disposition: attachment. The dashboard bundle proxy applies the same headers. SVG still works as an image resource.
Size limits
| Limit | Value |
|---|---|
| Per file | 10 MB |
| Total upload | 50 MB |
| Max files per version | 200 |
Deploying Sports Scoreboard v1.2.0...
Uploading 8 files (1.2 MB total)...
layer.js 312 KB application/javascript
editor.js 525 KB application/javascript
styles.css 24 KB text/css
assets/logo.png 89 KB image/png
...
Upload successful. Status: draft
Submit for review via the dashboard or `lumio status`.
Data schema
The tables in server/schema.ts travel with the version as data_schema (see Database schema). Lumio checks them at upload and creates them when the version is approved; a draft gets no tables. The upload fails with 422 when the schema is rejected, and the CLI prints why:
INVALID_DATA_SCHEMA- the server's message, for example a name outside^[a-z][a-z0-9_]{0,39}$, apg_*name or anext_*table name, an unsupported type, a.storage()scope other thaninstance, more than 32 tables or 64 columns per table, or a schema over 64 KiB.DATA_SCHEMA_NOT_ADDITIVE- one line per violation of the additive-only rule:
Deploy failed (HTTP 422):
Data schema change is not additive. Published tables only allow new tables, new optional columns and relaxing NOT NULL:
- points.score: type_change (applied: double precision, declared: text)
rule is type_change, new_not_null_column or not_null_tightening. Fix the schema and deploy the version again.
Backward compatibility
Older CLI versions that send a single bundle field continue to work. The API treats the single bundle as bundle.js and processes it through the same pipeline.
Direct API clients creating a version with POST /v1/developer/extensions/\{id\}/versions must supply a valid manifest whose extension ID and version match the request. Submitting a draft revalidates its stored manifest. Team-owned version creation and upload require team-versions:create; submission requires team-extensions:publish.
CI/CD deployment
In CI environments, authenticate with either the LUMIO_TOKEN environment variable or the --token flag:
# Via environment variable
LUMIO_TOKEN=${{ secrets.LUMIO_TOKEN }} lumio deploy -v 1.2.0 -d "Automated release"
# Via --token flag
lumio deploy -v 1.2.0 -d "Automated release" --token ${{ secrets.LUMIO_TOKEN }}
The CLI detects CI=true and skips interactive prompts. When --token is provided, it takes precedence over the stored credential.
Breaking changes
Mark a version as breaking to prevent auto-updates for users who have not manually reviewed the breaking change notes:
lumio deploy -v 2.0.0 --breaking -d "Storage format changed — manual migration required"
Breaking versions show a notice in the extension store and require users to manually accept the update.
Exit codes
| Exit code | Meaning |
|---|---|
0 | Deployment succeeded |
1 | Authentication error (run lumio login) |
2 | Validation error (missing build, invalid version, etc.) |
3 | Upload error (network failure, rate limit) |
4 | Extension not found (invalid Extension-ID) |