Skip to main content

Shipping a New Version of Your App

Who this is for: developers who already have an app deployed and want to ship a change — a new build, a new type, a new status, a new setting. Scope: what happens today, command by command and pointer by pointer. No roadmap, no "will." Related: Building apps on Starhive (what the manifest is) · Starhive CLI (every command and flag).

1. The two pointers, and who follows which​

An app has two pointers at any moment:

PointerMoved byWho resolves to it
latest deployedevery starhive app deployevery developer of the app, on every workspace
publishedstarhive app promoteeveryone else
deploy ──▶ latest deployed ──▶ every developer's own installs
│
│ promote
▼
published ──▶ every other install, on their next upgrade

That split is the whole release model: deploying changes nothing for customers. You can deploy twenty builds in an afternoon and every customer stays where they are.

2. The version number​

app.version in manifest.yaml is the version. There is no other source of truth, and nothing in the CLI bumps it for you.

app:
key: com.acme.tracker
version: 1.3.0

The rules, exactly as they are enforced:

  • The published version is immutable. Re-deploying the version that is currently published is refused.
  • Any other version is replaceable. Re-deploying a version that exists but is not published overwrites it in place: the manifest, the raw YAML, and the bundle are all replaced, and the version id stays the same. This is what makes the develop loop work — you iterate on 1.4.0 as many times as you like before promoting it.
  • Nothing reads semver. No comparison, no ordering, no "newer than." The published pointer is wherever you last pointed it, so promoting 1.2.0 after 1.4.0 is allowed and means exactly what it says. Use semver because customers read it, not because the platform does.

3. starhive app deploy​

starhive app deploy [--build] [--dir ./build] [--manifest ./manifest.yaml]
FlagDefaultWhat it does
--dir <path>./buildThe built bundle directory to zip and upload.
--manifest <path>./manifest.yamlThe manifest to publish.
--buildoffRuns npm run build first.
--env <prod|dev|local>prodWhich deployment to deploy to.
--api <url>—A specific app-platform URL. Only if Starhive tells you to.

Four HTTP steps, in this order:

  1. Publish the manifest. The YAML is parsed and validated, checked against the version customers are running (§10), and a version row is created or replaced.
  2. Request an upload URL for the zip.
  3. PUT the zip to that presigned URL. Files sit at the archive root, so index.html is at the top.
  4. Finalize, which flips the bundle to READY.
→ zipping ./build…
→ publishing manifest…
→ uploading bundle (42 KB)…
→ finalizing…

✓ Deployed 1.4.0
appId: …
versionId: …
bundleStatus: READY
serveUrl: https://…/index.html

What a deploy moves and what it doesn't:

  • It moves the latest-deployed pointer to this version, always.
  • It does not touch the published pointer, so customers are unaffected.
  • It does not change the app's marketplace status. A first deploy creates the app as DEPLOYED — not registered, not installable by anyone. Registration, marketplace details and pricing live in the web console (Marketplace → Manage your apps), not in the manifest.
  • It moves every workspace where you're a developer of the app — not opt-in, see §4.

A 401 is the token, not the bundle — re-run starhive access with a fresh PAT. A 403 on a version-up means you are not a developer of that app key; starhive app developers (see Building apps §11.3) shows who is.

4. Testing the build: development builds​

There is no per-installation "development builds" opt-in, and no toggle a workspace turns on to follow a developer's builds. Every workspace a developer installs the app into automatically follows the latest deployed build, with no toggle needed, and there is no way for a non-developer workspace (a customer's) to opt into pre-promotion builds. If you want a test bed, install the app as one of its developers; there's nothing to switch on beyond that, and no way to hand a customer's workspace this behavior deliberately.

One more thing worth knowing if you rely on the "Update available" signal to know whether a build is new: it compares an install's pinned version against the most recently deployed version of the app, regardless of promotion. So this signal alone doesn't distinguish a promoted release from a build still being tested; see §7 for what drives the update flow for non-developer installs in practice (the published pointer).

5. starhive app promote​

starhive app promote [--manifest ./manifest.yaml]

promote reads app.key and app.version from the manifest, finds that deployed version, and points the published pointer at it. It reads the registry only — it never moves the latest-deployed pointer, so your own builds are untouched.

Requirements:

  • You are a developer of the app.
  • That version exists and its bundle is READY. A version whose upload never finished is refused: version 1.4.0 has no ready bundle to publish.

What happens the moment it lands:

  • New installs and every customer upgrade (once triggered — see §7) now resolve to this version.

Promoting a version only changes what a future upgrade resolves to — it does not reach out and upgrade anything already installed. See §7 for how an upgrade actually reaches an installation.

→ resolving com.acme.tracker v1.4.0…
→ promoting 1.4.0…

✓ Promoted 1.4.0

Promote is the step that is felt — it's the only command here that changes what other people run.

6. When a promote waits for review​

Promote is never review-gated today. The app's status is exactly DEPLOYED, PRIVATE, PENDING_REVIEW, PUBLISHED, DECOMMISSIONED — there is no status for a live app waiting on Starhive to approve a promote. Promoting a version takes effect as soon as the caller is a developer and the version's bundle is ready; it does not inspect the manifest at all, let alone diff it against the previously-published one. Every successful promote takes effect immediately, for every version, with no review step of any kind.

7. How the new version reaches a customer​

An upgrade re-pins the install to whichever version currently resolves for that app (the published version for a non-developer, per §1/§4), then re-applies your data model changes. It's a no-op if the install is already on the target version. Whether a given upgrade fires automatically or waits for an explicit click in the web console is a detail of that screen — contact Starhive support if you need to know exactly when an upgrade takes effect for a particular install.

There is no distinct "upgrade in progress" or "upgrade failed" status — the status enum is exactly INSTALLING, NEEDS_CONFIG, ACTIVE, UNINSTALLED, FAILED. An installation that's mid-upgrade still just reads whatever it read before (typically ACTIVE). If an upgrade fails, the installation most likely stays exactly as it was before the attempt — still on the old version, still whatever status it had (typically ACTIVE) — rather than landing on a distinct failed state you can resume from. Clicking Update again attempts the same upgrade from scratch rather than resuming a tracked partial state.

StatusMeaning
INSTALLINGFirst-time setup running.
NEEDS_CONFIGA required setting is unset. Your settings page must be usable in this state.
ACTIVESet up and running — also what an installation reads while an upgrade is in progress or has failed against it, per the note above.
FAILEDDeclared in the enum, but not a status Starhive is currently seen putting an installation into in practice (see Building apps §9).
UNINSTALLEDSoft — the space and its data are kept, and a reinstall reactivates them.

8. Shipping a data-model change​

Your data block is not your schema. It describes things that live in the customer's workspace, holding the customer's objects, beside whatever their admins have added or renamed themselves. A new version is therefore not a migration you run — it is a description the platform converges on: it compares what you declared against what the workspace actually has, and makes up the difference.

Nothing enforces this convergence being safe. There is no deprecated field anywhere in the manifest schema — not on an attribute, type, or workflow state — and deploying a new version never loads or diffs the previously-published manifest. The only check that can block a deploy is re-deploying the exact version number that's already published (refused as immutable); nothing checks what the new manifest removes, narrows, or renames relative to any other version. The only validation deploy actually runs checks manifest-internal consistency: app.key format, app.version non-blank, at least one module, every ${config.*} binding referencing a real config field, every seed key referencing a real type, and workflow/transition/state consistency — it never looks at any other version.

In practice this means "keys are forever," "nothing is ever deleted from a workspace," and "widening is free" are true as observed effects of the reconciler being purely additive (see §9) — not because deploy enforces or refuses anything. You can deploy a manifest that removes a type your customers depend on, and it will succeed. Nothing retroactively touches what already exists in a workspace (the reconciler only acts on what's still in your current manifest), but nothing warns you that you've done this either. If you remove a key and later reuse it for something else, nothing stops you — and nothing protects you from the consequences in a workspace that still has the old thing.

9. What an upgrade actually applies​

ChangeReaches an existing install?
New typeYes — created
New attribute on an existing typeYes — created (except the type's label attribute)
Renamed type/attribute display name (key unchanged)Yes — but only if the tracked type/attribute can still be found in the workspace
New workflow (a whole new workflow key)Yes — created with whatever states/transitions it declares
New state or transition on an already-tracked workflowNo — the whole workflow is skipped once its key is tracked
New OPTION choice on an existing attributeNo — a tracked attribute's config is never revisited
required: true dropped on an existing attributeNo — cardinality is set once, at creation
cardinality: one dropped on an existing attributeNo — same
New labelAttribute on an existing typeNo — resolved only at type-creation time
seed rows added to a type that already existsNo — seeding happens once per type, ever; new installs only
Space name changeNo — no rename call exists for the space
A type/attribute deleted out-of-band (space still present)No — left stale rather than recreated
${config.*}-bound reference created or re-pointedYes, when the config value changes — finalizeBindings
Anything removed from your manifestNot refused at deploy — see §10

10. What deploy actually checks​

Deploy does not refuse breaking changes. It never loads or compares the previously-published manifest at all — the only refusal it has is re-deploying the exact version number that's already published (immutability, not a schema check). There is no deprecated: true-based workaround for anything, because there's nothing it would work around.

What deploy actually validates is manifest-internal consistency only:

  • app.key is non-blank and dot-notation; app.version is non-blank.
  • At least one module is declared.
  • Every ${config.*}-bound attribute target names a config field that's actually declared.
  • Every seed key names a type that's actually declared.
  • Every workflow has at least one state, no duplicate state keys, exactly one initial state, and every transition's from/to names a real state.
  • Every WORKFLOW attribute names a workflow that's actually declared.

There is no includeChildren field to flip (§5.6 of the building guide), and there is no deprecated field to reference in a workaround. :::

11. Retiring something​

There is no deprecated: true mechanism, or any other sanctioned way to retire a type, attribute, or workflow state. What actually happens if you just stop declaring something: per §9, the reconciler only acts on what's in your current manifest. A type or attribute you stop declaring simply stops being touched — it's neither recreated, renamed, nor deleted, and it stays fully resolvable (its id stays in typeKeyToId/attributeKeyToId on every installation that already has it, since those maps are only ever added to, never pruned). So in practice, today: the customer keeps everything (provisioning was never going to touch existing data anyway), and your app can still resolve the old key if you kept the id around client-side, since nothing withholds it. There is no protection against later reusing that key for something else, and no signal anywhere that a key was ever meant to be retired. If you need to stop using something, treat this as a documentation/discipline problem on your side, not a platform-enforced one.

12. Recipes​

Add a type or an attribute​

Declare it. Nothing else.

types:
- key: item
name: Item
labelAttribute: title
attributes:
- { key: title, name: Title, type: TEXT, required: true }
- { key: notes, name: Notes, type: TEXT } # new in 1.4.0
- key: sprint # new in 1.4.0
name: Sprint
labelAttribute: title
attributes:
- { key: title, name: Title, type: TEXT, required: true }

A new type gets its seed rows on the next upgrade, because that type has never been seeded on that installation. A new required attribute is created required, and every object that already exists is then missing a value for it — allowed, but required: false plus a default in your own write path is usually kinder.

Rename anything​

Names are matched by key, so a changed name is issued as a rename.

- key: item
name: Work Item # was "Item"
attributes:
- { key: title, name: Summary, type: TEXT, required: true } # was "Title"

Renaming a key is not refused — deploy doesn't diff manifests at all (§10). What actually happens: the old key's type/attribute simply stops being managed (it's not in your manifest anymore, so the reconciler ignores it — it stays in the workspace, unrenamed, unseeded, with whatever data it has), and the new key gets created fresh as if it were brand new. Nothing stops you from doing this; nothing warns you either. If you want the old one gone from view, that's a manual conversation with the customer today, not something the platform does for you.

Add a workflow status​

Adding a blocked state and new transitions to an existing lifecycle workflow does not reach already-installed workspaces: a workflow whose key is already tracked is skipped entirely once it exists. A new state or transition added to an existing workflow key is only ever seen by a new installation from that point on; it never reaches a workspace that already has that workflow. There is no way to get a new state or transition into an existing install today — a new workflow key would be provisioned fresh, but that's a different workflow, not an addition to the old one.

Add an OPTION choice​

A tracked attribute is only ever checked for a rename; its options are never re-read once the attribute exists. Appending a choice to an existing OPTION attribute's manifest declaration has no effect on installations that already have that attribute — same limitation as workflow states above. A brand-new OPTION attribute picks up whatever choices you declare at creation time, same as any other new attribute.

Relax a bound​

Dropping required: true or cardinality: one on an attribute that's already provisioned has no effect — a tracked attribute's required/cardinality are never revisited after creation. This only does something meaningful for an attribute you're declaring for the first time in this version, which is created fresh with whatever bound you wrote.

Move the label​

A type's label attribute is resolved and set only once, at type-creation time — there's no way to re-point an existing type's label at all. Changing labelAttribute in a later version's manifest has no effect on installations that already have that type; it only matters for a type being created for the first time.

Change the space​

space.access isn't a manifest field at all — the manifest only has key and name for a space — and a space is only ever created when there's no tracked space yet, with nothing to rename it afterward. Changing name in a later version has no effect on an installation that already has the space provisioned.

Re-point a ${config.*} reference​

Not a manifest change at all — it is the admin changing a setting on your settings page, which re-points the binding at the type they picked, on the spot.

Changing a static targetType (not config-bound) in the manifest is neither applied nor refused: deploy doesn't diff manifests (§10), and a tracked attribute is only ever re-checked for a rename, never re-targeted. So changing a static targetType in a later version has no effect on an already-created attribute; it would only take effect on a brand-new attribute.

config:
- { key: linkedType, name: Linked type, type: typeRef, default: item }
types:
- key: timeEntry
attributes:
- { key: workItem, type: REFERENCE, targetType: "${config.linkedType}", cardinality: one }

13. Known limits​

  • No data migrations. There is no way to backfill a value onto existing objects, or convert one attribute's values into another's.

  • No upgrade preview. There's no diff shown before an admin updates an installed app to a new version.

  • A tracked type or attribute that's gone missing from the workspace is silently skipped, not recreated — only a deleted Space triggers full recreation. An already-tracked workflow is never revisited regardless of whether it still exists (§9).

  • There is no deprecated: true mechanism, so there's no reserved-key protection, and retired states are not withheld as destinations (§11).

  • There is no key-reservation mechanism, so nothing accumulates and there's no price paid for reusing a key. The actual risk runs the other way: reusing an old key for something new is not protected against at all (§11).

  • Installing and upgrading are web-console actions or direct API calls. The CLI ships versions; it does not install or upgrade them.

14. Release checklist​

  • app.version bumped, and the build you are shipping is the one you tested.
  • data.types and the attribute names your code reads still match.
  • Installed cleanly into a fresh workspace, reaching ACTIVE.
  • Upgraded a workspace that was on the currently published version, not just a fresh install — that is the path every customer takes. Check specifically for the gaps in §9: a new workflow state/transition, a new OPTION choice, or a relaxed bound on an existing attribute will not show up after an upgrade the way you might expect from the original version of this guide — only brand-new types/attributes/workflows do.
  • Every declared slot renders; the settings page works with nothing configured.
  • Errors surface as toasts, not blank frames. Light and dark both fine.
  • starhive app promote — this takes effect immediately for every version; there is no review wait to expect (§6).