Scoping

How a scope dimension such as market or channel slices your rows inside one tenant — the scopeable flag, the Entity Scoping Engine, and providing a dimension with provides_scopes.

Tenant isolation separates one customer from another and you cannot switch it off. Scoping is the other axis: slicing rows within one tenant by a business dimension, and it is entirely opt-in.

A market is the dimension you will meet first — a business segment inside a tenant, such as a country, a region, a brand storefront, or a B2B versus B2C split. Scoping lets one app deployment behave differently per segment without duplicating tenant data. channel works the same way.

Two mechanisms do the work, and an app opts into them separately:

  • Scoped rows — an entity declared scopeable by a dimension has its row visibility filtered by the caller's active scope context. That is this page.
  • Scoped settings — a settings.json key declared scope: "market" can hold a different value per market. That is Settings and Reading settings.

Where the dimension comes from

market is not built into the platform. It is a scope dimension, provided by the markets app through the Entity Scoping Engine:

manifest.json
"provides_scopes": [
  {
    "dimension": "market",
    "slug_source": "markets.code",
    "jwt_path": "scope_context.market",
    "match_mode": "single"
  }
]
FieldRequiredDescription
dimensionYesGlobally unique snake_case slug. One provider per dimension platform-wide.
jwt_pathYesDot-path into the token claims carrying the caller's active context.
match_modeYessingle — the claim is one slug. any_of — the claim is an array and any overlap matches.
slug_sourceNoDocumentation-only pointer to where the slugs live, e.g. markets.code. No constraint is enforced.

Declaring a dimension makes it available platform-wide, so any other app can then scope its entities by it. There is exactly one provider per dimension — a second app claiming market is rejected. market is not the only one: the channels app provides channel the same way.

You do not declare provides_scopes to use markets. You declare it only if you are the app that owns a new dimension.

Scoping your rows

Mark the entity scopeable:

schema.json
"entities": {
  "device_serials": {
    "columns": {
      "id": { "type": "uuid", "pk": true, "default": "gen_random_uuid()" },
      "serial": { "type": "text", "notNull": true }
    },
    "scopeable": ["market"]
  }
}
ValueMeans
false (default)Not scoped.
trueScopeable by every registered dimension.
["market"]Scopeable by exactly the dimensions listed.

A scopeable entity requires a uuid id column. Declaring the dimensions explicitly is what the platform's own apps do — every scopeable entity across them says ["market"] rather than true, because being scoped by a dimension nobody expected is harder to reason about than adding one later.

Row visibility is then filtered by the caller's active scope context, which the platform resolves per request from the token. You write no filter, and there is no scope column in your columns for you to manage: the assignment lives with the engine, not in your table.

If the entity also needs the dimension's own app installed, declare the dependency:

manifest.json
"dependencies": { "revenexx/markets": "^0.1" }

Scoping is open by default

This is the part worth knowing before you rely on it. A row carrying no assignment for a dimension matches every value of that dimension. A product assigned to no market is on sale in every market.

That is usually the behaviour you want for existing data — nothing disappears when scoping is switched on. It is also not always what a merchant wants, which is why the apps that own a dimension expose a tenant setting to invert it: a segment can be declared closed, so a row appears in it only when explicitly assigned. Whether that inversion applies to your entity is the dimension owner's decision, not yours.

Requiring an assignment for correctness — as a contract or an assortment does — means you cannot lean on the default and must check the setting.

Relationship to storefronts

One storefront can serve several markets, or each market can have its own. That mapping is the tenant's configuration, not something your app declares. Your app declares which of its entities scope by a dimension and which of its settings vary by it; everything else is the merchant's arrangement.

Next steps

Was this page helpful?