Why apps cannot collide
You do not have to check whether your app overlaps with anyone else's. Two apps on the same tenant cannot break each other. No naming coordination with other vendors, no load order, no defensive code against a plugin you have never seen.
This page is for developers who have been burned by plugin conflicts elsewhere and want to know why the same risk does not exist here. It states the guarantee, walks through the boundary that makes it hold, and is honest about the places where apps do meet and what is and is not guaranteed there.
The guarantee
Your app's tables, routes, events, settings and admin screens belong to your app. Another app cannot read your tables, alter your schema, answer on your routes, or change what your code sees. The only way into your app is the capabilities you publish, and the only way out is the capabilities you have declared you will call.
Concretely, the following are not your problem:
| You might expect to | You do not, because |
|---|---|
| Prefix your table names so they do not clash with another vendor's | Every table is namespaced by your vendor and app name. Two apps can both own an orders entity. |
| Check whether another app already uses a URL | Every route lives under your app's own prefix, and the gateway routes by it. |
| Worry about load order or hook priority | There is no hook chain. Nothing runs inside your process but your code. |
| Guard against another app's exception taking your route down | Another app's failure reaches you only as an HTTP status on a call you made. |
| Coordinate event names | An event is published as <vendor>.<app>.<name>, so serial.registered in two apps are two events. |
| Fear an upgrade of one app silently changing another | Contracts are versioned strict-forward. A breaking change is a new capability key on a new route. |
Why it holds
The guarantee is not a policy. It follows from four boundaries, each of which you can verify from your own manifest and from the errors the platform returns.
1. Your schema is private
Every entity in your schema.json becomes a table named {vendor}__{app}__{entity}, so acme/serials declaring device_serials produces acme__serials__device_serials. Nothing you declare can land on another app's name.
The privacy runs the other way too. A references block that names another app's entity is rejected when the app is applied, and no entry in manifest.permissions can name another app's entity at all. The one thing another app can see of your data is what you put in a lookup block, and that is only the label and the columns you list.
So one app's schema change can never break another app's tables, and no app can be blocked from deleting a row by a foreign key it has never heard of. See Relationships.
2. Nothing reaches your code except through the gateway
Your function has no address anyone can call directly. The public gateway at api.revenexx.com is the only entry, and it forwards a request to your app only when the request matches a route your manifest.capabilities.json declares. An undeclared route in your function is unreachable from outside, and a declared one is served under your app's own prefix.
The same is true of the other direction. Your app gets no access by default. To call another app you declare a capability grant, and an undeclared call is refused. The merchant sees that grant on the consent screen at install time. See Permissions and Calling another app.
3. Every invocation is one tenant, and one that installed you
Your function is invoked for a tenant only when that tenant has your app installed and active. The tenant comes from the request context the gateway verified, never from a header or body a caller could forge, and every read and write your code performs is scoped to that tenant by the platform. There is no query you can write that forgets it, and there is no query another app can write that reaches your rows. See Tenant isolation.
4. Apps do not share a process
Each app deployment runs in its own runtime, per tenant, with its own CPU and memory allocation and its own execution timeout. There is no shared process, no global namespace, and no shared library another app's upgrade could break underneath you. When another app fails or runs out of time, you see it as a failed response to the call you made, and nothing else changes for you. See Whose fault is it.
Compared with the plugin model
If you have shipped plugins for Shopware, Magento or WordPress, the mental model you bring is the one to unlearn:
| Plugin platforms | revenexx Apps |
|---|---|
| Plugins share one database and one schema; a table name is a global claim | Tables are namespaced per app; the schema is private |
| Plugins share one process; an uncaught exception in one takes the page down | Each app is its own function; failure is an HTTP status the caller handles |
| Behaviour is composed through hooks, so load order and priority matter | Behaviour is composed through capabilities and events; there is no order to get right |
| Two plugins patching the same class conflict, and the merchant finds out in production | Behaviour is replaced through an explicit, per-tenant override of a versioned contract; the contract itself never changes |
| A plugin can read and write any table it can name | An app reads its own tables and reaches other apps only through declared, consented capability grants |
Where apps do meet, and what is guaranteed there
Isolation does not mean apps ignore each other. They compose. Each meeting point is deliberate, and each has a stated guarantee and a stated limit.
Capability calls. The interface between two apps is the contract in manifest.capabilities.json: route, request shape, response shape, declared statuses. Versioning is strict-forward, so a published contract may gain things and never change the meaning of what it already published. What is not guaranteed is that the other app is answering. Installed is not the same as available, so handle a 502 as a 502. See Versioning.
Overrides. An app can implement a capability another app defined, and for the tenants that install it the gateway routes that one contract to the new implementation. The contract does not change, and every caller keeps working unchanged. No shipped app uses an override yet, and how two installed implementers of the same key on one tenant are resolved is one of the details that will firm up with the first real use. See Overriding a capability.
Events. Names are unambiguous across the platform because they carry the emitter's vendor and app. Delivery semantics, such as ordering and retries, are not settled enough to document, so write handlers that survive a duplicate, a reorder and a missing event. See Events you receive.
Dependencies. A semver range in dependencies guarantees the other app is installed when yours is activated, and a deploy whose dependencies cannot be satisfied is refused rather than warned about. It does not stop a merchant uninstalling it later, and it gives you no access to its tables. See Dependencies.
App identity. vendor/app is unique across the platform. A second app claiming the same vendor and name is refused, never merged, and the vendor slug belongs to your organisation. Within your vendor, naming is yours alone.
The Cockpit sidebar. Every installed app's cockpit.json is merged into one interface, ordered by group and position. An app that claims a navigation id owns it; another app may attach children under it. Two apps claiming the same id is a conflict the Cockpit reports rather than resolves. See How the Cockpit renders.
Scope dimensions. A dimension such as market has exactly one provider platform-wide. A second app declaring the same dimension is rejected. See Scoping.
Business meaning. Two apps on one tenant can both act on the same business fact through capabilities, for example two apps that adjust stock. The platform guarantees each call is authorised and scoped; it does not arbitrate which app is right about the business. That remains a design question for the tenant and the apps it installs.
What this page does not describe
The runtime machinery behind these boundaries is not part of the contract you build against, and it is not documented here. What is documented is the behaviour you can rely on and the errors you will see when a boundary is crossed: a rejected cross-app reference when the app is applied, a refused undeclared capability call, a null for another tenant's row, a reported Cockpit conflict.
Next steps
- App model — the files that declare each of these boundaries.
- Permissions — the access register, deny by default.
- Tenant isolation — the other axis, between customers rather than between apps.
- Calling another app — composing across the boundary on purpose.