Pairing an app and a frontend

Planning a Marketplace listing for a solution that spans an app and a storefront — why they ship as two artefacts, how a theme declares the capability it needs, and the order a customer installs them in.

Plenty of solutions are two halves: an app that holds the business logic, and a storefront that shows it to a buyer. This page is for the vendor planning that listing.

The short answer: they ship as two artefacts, and you link them with a capability requirement rather than a bundle. There is no single listing that installs both.

Two artefacts, on purpose

An app and a frontend are separate packages with separate lifecycles. Nothing about that is a limitation you are working around. It is the shape that makes the pairing survive change.

The appThe frontend
Manifestmanifest.jsontheme.json
Commercial termsbilling.jsonbilling.json
Reachmanifest.type: public or privatetheme.json#type: public or private
Ships withrevenexx deploy apprevenexx deploy theme .
Installing writesthe capability routes for that tenantthe theme onto the tenant, then you activate it per domain
Version rulestrict-forward semverpublished versions are immutable

The reason to keep them separate is the version cadence. A published theme version cannot change, and an app version bump is routine: a fix, a new capability, a new setting. Bundling them into one artefact would mean republishing the storefront every time the logic behind it moved, and asking every customer to reinstall a frontend that did not change.

Separate artefacts also let one app serve several frontends, and let a customer keep their own storefront while installing your app underneath it. That is the common case in practice, not the exception.

One listing does not cover both halves

Two manifests means two registrations, two publish steps, and two things the customer installs. Neither manifest can pull the other in: theme.json has no dependency block at all, and an app's dependencies — which does auto-install — names apps, not frontends.

So a solution that spans both halves is two listings, and the vendor's job is to make that legible: say in each listing's description that the other half exists, and name it. The description and the reason fields are what a merchant reads before they trust you. See Billing for what the listing block holds, and Private or Marketplace for who can install each.

The frontend declares what it needs, in theme.json:

theme.json
"requires": [
  {
    "capability": "products.list",
    "compatible": "^1.0",
    "reason": "renders the product grid on the home and category pages"
  }
]

This is a hard dependency, enforced at install. A theme that requires products.list cannot be installed on a tenant with no app providing it: Cockpit blocks the install and names the missing capability. It does not name the app that provides it, so say plainly in your listing which app a customer needs. The reason field is read by the human doing the installing, so write it for them.

Note what the theme names: a capability, not an app package. That is the design worth understanding, because it is why the two halves can stay decoupled. The requirement is on the capability key, so whichever app provides products.list for that tenant satisfies the theme. Note what the install gate actually checks: that the capability is routed at all. The compatible range travels with the requirement and is reported back when the gate fails, but it is not compared against the installed version — treat it as a record of what you built against, not as a version gate. You are not pinning your storefront to one vendor's app, and a customer who later swaps the app underneath does not need a new theme.

Today that flexibility is more theory than practice: every capability in every app the platform ships is a define, so in reality a capability is served by the app that defined it. The contract-level requirement is still the right thing to declare; it just does not yet have a second implementation to resolve to. See Overriding a capability.

For the app-to-app version of the same problem, one app needing another, the mechanism is different and stronger: dependencies installs automatically, peerDependencies prompts the merchant. See Dependencies.

An app cannot declare its companion frontend. There is no field in manifest.json naming a theme, a site, or a recommended storefront, and no way to make installing your app offer the frontend that goes with it.

Practically, that means the frontend is the half that carries the relationship. Plan for it:

  • Put the requirement in the theme's requires[], where the platform enforces it.
  • Put the pairing in both listings' descriptions, where a human reads it.
  • Do not expect a merchant who installed your app to be led to your storefront. Tell them, in your own documentation and in the listing.

What the customer does, and in what order

The order is not a recommendation. The theme install is blocked until the app is there.

  1. Install the app, from the Marketplace. They read the listing, see the access it asks for on the consent screen, and install. The capability routes are written for their tenant at this point. See Publish and install.
  2. Install the frontend, from the Theme Marketplace in Experience Studio. Its requires[] is checked here. With the app already installed this succeeds; without it, Cockpit blocks the install and names the missing app.
  3. Activate the frontend on a domain. Installing makes the theme available; activation is what binds it to a domain. This step is per domain rather than per tenant, which is what lets one deployed theme serve many customers. See Publishing a theme.

If a customer does it in the other order, they hit a blocked install with a message naming what they need. That is a decent failure, self-explanatory and recoverable, but it is still a support email you can avoid by stating the order in your listing.

Planning checklist

For a solution that spans both halves:

  • Decide the reach of each half separately. A private theme for one customer paired with a public app is a normal combination.
  • Declare the capability requirement on the theme, with a reason written for the person installing it.
  • Declare only what the theme genuinely cannot run without. Every entry in requires[] is another install that can be blocked.
  • Name the other half in both listings. The platform will not do it for you.
  • State the install order in your listing and your own docs.
  • Treat your capabilities as a published contract from the first version. Nothing today lets you ask which themes or apps depend on one, so removing a capability surfaces as somebody else's storefront breaking rather than as a failure at release.

Next steps

Was this page helpful?