Choose where the logic goes

Five places a piece of business logic can live on revenexx — an App, a Theme, an Integration Studio workflow, your own service, or an override of an existing capability. Four questions that pick one, a side-by-side comparison, and eight typical requirements with where each lands.

This page is for developers and solution architects who have a requirement in hand — a price rule, an approval step, a nightly ERP push — and need to decide which part of the platform runs it. By the end you'll have a home for each requirement in your design, and you'll know which of the five options are proven today and which are not (as of 2026-09).

The decision matters early because the wrong answer is quiet. A rule written in a Theme works on the first storefront, then gives a different answer in Cockpit, in a workflow and in a partner's own client. Moving it later means changing every place it was copied.

The short answer

Ask the four questions below in order. The first one that has a clear answer usually decides it.

  1. Who consumes the result? If one storefront shows it differently from another, it's presentation, and it belongs in the Theme. If every channel must get the same answer — a price, a stock figure, a validation rule — it belongs in an App.
  2. Does a caller wait for it? If a buyer or another system is waiting on the response, build an App capability. If it can run later, on a schedule or after an event, an Integration Studio workflow fits (it is experimental, as of 2026-09), or an App handler where the event is one of the verified ones.
  3. Does it need its own data model or a Cockpit screen? Then it's an App. None of the other four options gives you tables or admin screens on the platform.
  4. Does it call an external system? A multi-step flow that pushes data into an ERP, PIM or CRM belongs in an Integration Studio workflow. A synchronous lookup inside a request belongs in an App, which declares the host under outbound; that declaration is a record of intent, not yet a block on other hosts (as of 2026-09). Code that has to run on infrastructure you operate is your own service.

A fifth option, an override, is different in kind: it doesn't add new logic, it replaces the code behind an existing capability. Reach for it last, and read what an override can and can't promise first.

Side by side

AppThemeIntegration Studio workflowYour own serviceOverride
RunsOn the platform, as a Node functionOn the platform, as a server-rendered storefrontOn the platform, as a durable runOn infrastructure you operateOn the platform, in place of another App's function
Who can use the resultEvery channel: storefronts, Cockpit, workflows, integrations, SDKsThe buyers of that one storefrontWhatever the workflow writes toWhatever you build it forEvery caller of the capability, unchanged
A caller can wait for itYes, an HTTP capability under /v1Yes, for that pageOnly if the workflow allows synchronous runs (the default is asynchronous), and then for at most 30 secondsYes, your own latencyYes, and its latency is now in the caller's path
Own data model or Cockpit screenYesNo, page content is data the platform storesNo, it moves data, it doesn't model itYour own databaseNo, the contract stays as shipped
Calls an external systemThrough a declared outbound host, not yet enforced (as of 2026-09)Not its job: its server routes call the public APIYes: credentials, nodes, retriesYes, freelyYes, that's usually why you override
MaturityShipped, with gaps: outbound not enforced, five grants unproven, two verified events (as of 2026-09)No caveat documented (as of 2026-09)Experimental, schemas are drafts (as of 2026-09)Not applicableNo shipped App uses it (as of 2026-09)

An App

An App is one Node function plus declarative JSON contracts: a data model, capabilities served under /v1, optional Cockpit screens, schedules and event handlers. It's where logic lives when the answer must be the same no matter who asks.

Choose it when the rule has consumers beyond one storefront, when it needs tables of its own, or when a person in Cockpit has to see and change its data. A capability is part of the API, so the same declaration produces the OpenAPI entry, the SDK method and the Explorer page. Don't choose it for pure presentation or for a one-off integration flow.

An App handler reacting to an event is narrower than it looks: only app.installed and app.uninstalled are verified (as of 2026-09). For reactions to business events, use Choose an event surface before you design around a handler.

Read next: Build your first App to build one, App Studio for the model, and Private or Marketplace for how it reaches a tenant.

A Theme

A Theme is the front end a Site runs. It owns blocks, layout, branding and how the buyer's session is handled, and it reaches platform data only through the public API, from its own server routes.

Choose it for anything that is about how one storefront presents an answer: which blocks the home page has, how a product card looks, what order the checkout form asks its questions in. The split is worked through, with examples, on Apps and Sites.

Don't choose it if a second channel would need the same logic; that logic belongs in an App. To build one, start with Build your first Theme, or Connect your own frontend if the storefront is not a Theme.

An Integration Studio workflow

A workflow runs as a durable sequence of steps against one tenant, with each step's result recorded and a failing step retried. It's the place for moving data between the platform and a customer's ERP, PIM or CRM without hosting any code.

Choose it when the work is an integration rather than a rule: read orders out, write products in, import a file on a schedule. Stay on asynchronous runs unless a caller really needs the answer in its response, because a synchronous workflow run is cut off after 30 seconds with a 504 and keeps running in the background. Execution modes has the detail, including why every write should be idempotent.

Integration Studio is experimental and its schemas are drafts (as of 2026-09), so a workflow built today may need a migration later.

Don't choose it when a caller waits on the answer, or when the rule must give every channel the same result; that is an App's job.

Read next: Reaching Revenue Cloud for the three ways a workflow talks to the platform, and Hand an order to the ERP for one worked end to end.

Your own service

Some logic belongs on infrastructure you run: it already exists there, it has to sit next to a system of yours, or you need full control of its runtime. Your service talks to the platform the way any API client does, with an API key and the tenant header, and learns about changes through a webhook.

Choose it when the platform's runtimes don't fit. Don't choose it just to avoid learning the App model: the price is that you run, secure and monitor it yourself.

If the only reason is a callback from a third party, you may not need a service at all: a POST capability of your own is a public HTTPS route, and you check the caller's signature in the handler. Not yet proven has that pattern.

An override

An App that implements a capability another App defined takes over that route for the tenants that install it. The URL, the request and the response stay as the contract says; only the code behind them changes. Three capabilities are designed to be replaced: prices.resolve, inventories.availability and payments.webhooks.ingest.

No shipped manifest uses implement today (as of 2026-09). Every capability in every shipped App is a define, so there is no production override to learn from. Two things follow for your design:

  • Prove it before the architecture depends on it. Deploy a trivial implementation to a staging tenant, call the route, and check that X-Capability-App names your App.
  • Keep a fallback. Defining your own capability on your own route and having the customer's storefront call it loses the drop-in property, but the contract is yours.

payments.webhooks.ingest has an extra catch: an App that implements it can't write the payment ledger, so an override there stops provider callbacks from moving payments (as of 2026-09). To feed payment facts from an ERP, follow Confirm invoice payments from your ERP. The rest is on Overriding a capability.

What the platform enforces

For the placement decision one fact matters: an App's manifest.permissions list is a consent screen, and only three of its nine grants (entity, identity and capability) are enforced (as of 2026-09). Design as if the platform won't stop an undeclared call to anything else, and don't quote the list to a customer's security review as a guarantee. Permissions tracks the current state, Person permissions covers the separate person-level 403 check, and Answer a customer's security questionnaire shows how to state it.

Eight requirements and where each lands

RequirementWhere it landsWhy
Which cost centres exist and which buyer may use whichAn App capabilityEvery channel needs the same answer
The cost-centre field in checkout, its label and positionThe ThemePresentation in one storefront
A store finder listing warehouses and shopsAn App capability for the list, a Theme block to show itThe data has other consumers, the block doesn't
Prices computed by an external systemYour own App capability that calls the system, or an override of prices.resolve once it is proven on stagingOverrides are unproven, so the capability you author is the safe default (as of 2026-09)
Stock that lives in an ERPYour own App capability, or an override of inventories.availability once it is proven on stagingThe same trade-off as prices
A nightly push of orders to an ERPAn Integration Studio workflow on a schedule, asynchronousNobody is waiting, and the flow needs retries
An approval only a person may giveAn App capability named in a provides_permissions entry's capabilities, with require_principal: trueThe API gateway refuses a call with no person and a person without the permission
A callback from a third-party serviceA POST capability of your own, checking the caller's signatureA capability is a public HTTPS route, no extra grant needed

Where to go next

Was this page helpful?