Dependencies
An app can need another app. There are two blocks for saying so, and the difference is who installs it.
"dependencies": { "revenexx/markets": "^0.1", "revenexx/orders": ">=0.6" },
"peerDependencies": { "revenexx/prices": "^1.0" }
| Block | Installed how | Use for |
|---|---|---|
dependencies | Automatically, when your app is activated for a tenant | Apps yours genuinely cannot run without |
peerDependencies | The merchant is prompted and installs it themselves | Paid or genuinely optional dependencies |
Which block
dependencies is for something your app is broken without. A market-scoped setting needs the markets app; an entity scoped by market needs it too. Declaring it means the merchant never ends up with your app installed and its foundation missing.
peerDependencies is for a dependency that is a genuine decision. Use it when the dependency costs money, when the merchant may already have a competing app filling that role, or when your app degrades gracefully without it. The merchant gets a prompt rather than a silent install and a surprise line on an invoice.
The test: would installing this app on the merchant's behalf, without asking, ever be the wrong thing to do? If yes, it is a peer.
The key rule the JSON Schema does not enforce
Keys must be vendor/app.
"dependencies": {
"revenexx/markets": "^0.1"
}
"dependencies": {
"markets": "^0.1"
}
A bare app name is rejected when the app is applied — not when the JSON Schema validates it. The schema sees a valid object with a string value, so your editor is happy, npm test is happy, and the failure arrives during the deploy.
This is the single most common manifest mistake, and it costs a build every time. Check both blocks before you deploy: every key has exactly one slash, the part before it is a vendor slug, and the part after it is an app name.
revenexx is the vendor for platform apps. Your own apps use your organisation's slug.
Version ranges
Values are semver ranges. The usual operators apply:
| Range | Accepts |
|---|---|
^0.1 | Compatible releases up to the next major |
~1.2.0 | Patches of 1.2 only |
1.0.0 | Exactly that version |
>=0.6 | That version or anything newer |
Prefer ^. It picks up fixes and features and stops at the next major, where breaking changes are allowed to land. An exact pin never changes underneath you and also never gets a fix until you bump it by hand — and a routine patch in a dependency leaves you demanding a version nobody ships any more.
>= with no upper bound is the opposite risk: it accepts a future major whose breaking changes you have never seen.
A dependency is a runtime relationship, not a data one
Declaring a dependency does not give you access to that app's tables, and it does not let you write a foreign key across the boundary. Those are rejected. See Relationships.
Reaching another app is always its capabilities, and calling one always needs its own grant:
"dependencies": { "revenexx/orders": ">=0.6" },
"permissions": [
{ "capability": "orders.get", "compatible": "^1.0" }
]
Those two lines do different jobs and you usually need both:
dependenciesmakes sure the app is there.permissionsmakes you allowed to call it, and puts it on the merchant's consent screen.
A grant without a dependency is an app that works on tenants that happen to have the other app installed and fails on the rest. A dependency without a grant is an app that is installed and unreachable.
Practical guidance
Declare the fewest dependencies you can. Each one is another app in the merchant's tenant, another version range to keep current, and another thing that can be uninstalled underneath you.
Handle the dependency being unavailable anyway. Installed is not the same as answering. A capability call that fails is a 502 you translate, not an exception that takes your route down. See Calling another app.
Do not depend on an app for one field. If all you need from another app is a label for an id, a lookup resolves it in the UI with no dependency and no call at all.
Re-read both blocks before a major. A dependency you no longer use is a grant you no longer need and a prompt the merchant no longer has to answer.
Next steps
- Manifest reference — both blocks in context.
- Calling another app — the grant that goes with the dependency.
- Versioning — what a range is protecting you from.
- Validate — catching the
vendor/appmistake before a deploy.