Docs

Running a store

DocsRunning a store

Permissions

5 sections10 min readdocs/permissions.md

On this page5 sections
  1. 1The model
  2. 2Setting up the common roles
  3. 3Why can’t my admin see something?
  4. 4Integration tokens are not admins
  5. 5Permission reference

How to decide who in your organisation can do what, and how to configure it.

This is the operator’s guide. If you are writing a resolver and need to know which guard helper to call, you want permissions-enforcement.md instead - that one covers the enforcement patterns and the static coverage test.

The model

An admin gets permissions through roles. A role holds a list of grants, and each grant is a triple:

(permission, channel?, locale?)

Both scope columns are optional, and leaving one empty means “any”:

ChannelLocaleThe grant applies to
anyanyeverything - a global grant
EUanythe EU channel, in every locale
anydeGerman content, on every channel
EUdeonly EU’s German content

Grants stack. An admin’s roles are unioned and the broadest grant wins - if one role gives catalog.manage on EU and another gives it globally, the global one applies. There is no deny rule; you cannot subtract a permission by adding a narrower grant.

Two rules that surprise people:

  • manage implies view, never the reverse. Every resource registers a <resource>.view / <resource>.manage pair, and manage is a strict superset - an admin with only orders.manage passes an orders.view check. So granting both is redundant; grant manage alone.
  • Superusers bypass everything. The check short-circuits before roles are consulted, so a superuser’s role assignments are decoration. Use the flag sparingly - it is not scoped, auditable per-resource, or revocable per-channel.

There is no all_channels toggle. “Global for X” is derived: it means at least one grant row for X has both scope columns empty.

Setting up the common roles

Channel manager - runs one storefront end to end, can’t touch the others. Grant the resource manage slugs with Channel set to that channel and Locale left as any. They will see only their channel in the picker, only their channel’s orders and customers, and get a FORBIDDEN on anything belonging to another channel.

Translator - edits German copy everywhere. Grant the content manage slugs with Channel left as any and Locale set to de. This is why locale scope exists independently of channel scope.

Support - reads everything, changes nothing except what they must. Grant the view slugs globally plus the specific manage slugs their job needs, e.g. orders.refund.

Read-only auditor - the view slugs globally, nothing else.

Roles are edited at /admins/roles/:id in the dashboard, under Permission grants. Add one row per (permission, channel, locale) combination; add the same permission twice to grant it on two different scopes.

Why can’t my admin see something?

Work down this list - it is roughly the order of likelihood.

  1. They have view but the thing is channel-scoped elsewhere. List queries filter silently rather than erroring, so a scoped admin sees a short list, not a warning. A product detail page will also render with no listing rows for channels they can’t see.

  2. They have the permission on the wrong scope. A grant on (EU, any) does nothing for a US order. Check the grant rows, not just the permission name.

  3. The action touches a row shared across channels. Product content, attribute values, media, downloads and subscription terms are one row rendered by every channel that lists the product, so editing them requires the permission on every listing channel (or a global grant). A single-channel admin is refused on a product listed in two channels - deliberately, since the edit would otherwise leak into a channel they can’t see. If you need genuinely per-channel copy, give each channel its own locale.

  4. It is a global-only resource. These need a global grant, and a channel-scoped grant never satisfies them:

    • install settings: currencies, countries, locales, the audit log, the scheduler, cache purges, the email suppression list, and the config-orphan actions;
    • access: admin users, roles and integration tokens (and nobody can hand out a grant wider than their own);
    • shared catalog and content: product types, attribute groups and definitions, collections, CMS schema, snippets, block types and tags, blog authors, categories and tags, inventory locations, and edits to shared media;
    • install-wide rows: webhooks, remote providers and gift cards with no channel, promotion rule ordering and simulation;
    • importing products or categories, and any data export or import that names no channel.

    Creating a new CMS page, blog post, category or menu only needs the permission somewhere, since the new row isn’t listed on any channel yet. Adding it to another channel, or taking a listing away, needs the permission on every channel that already lists it, so one store can’t claim another store’s page and lock its admins out.

  5. They have zero grants for the slug. Then they see an empty list and no buttons, which is correct.

Integration tokens are not admins

API tokens for machine callers authenticate separately from admin roles. A token holds a flat set of permission slugs and, optionally, a channel pin: an unpinned token’s grants hold globally, while a pinned token’s grants only satisfy checks scoped to that one channel - global gates refuse it and channel-filtered lists show only its channel. Pin the token for anything single-storefront (a feed export, a per-store sync), grant the narrowest set of slugs it needs, and issue one token per integration so you can revoke them independently. There is no locale scoping on tokens.

A token can do whatever its slugs allow, the same as an admin holding them - moderate reviews, adjust store credit, run imports and exports, edit the catalog. What it does is recorded against the integration rather than a person: the audit log names it (filter the log by integration to see everything one system did), and so do the records that keep an author, like a moderated review, an answered question, a store-credit adjustment or a data job. Inventory syncs that name no source are labelled integration:<name>.

A token can also let an AI agent read the store over MCP, with the same slugs deciding which tools it gets - see mcp.md.

A few things only a person can do, and a token gets a clear ADMIN_ONLY refusal there: your own account, password, two-factor and sessions, your preferences and notification inbox, AI chat, draft preview links, claiming a CMS page, acting as a customer, and managing admins and other tokens.

Permission reference

Slugs are registered in code - each domain package declares its own in init() - and this table is generated from that registry by skelet gen, so it cannot drift from what core declares.

The naming convention is <resource>.view and <resource>.manage, plus extras where an action deserves its own gate (orders.refund, reviews.moderate).

Grants carries the registry description where one is written, and otherwise the display name - which for most slugs just restates the slug. The descriptions exist for the grants where the name undersells the power: orders.refund moves money, admins.manage can escalate to anything else. Those are the rows worth reading before you hand them out.

The roles UI can list slugs this table doesn’t, for two reasons, and the difference matters when you are debugging a grant that seems to do nothing:

  • Enabled plugins register their own. Those are runtime additions and belong to the plugin, so they aren’t part of the core reference.
  • Removed permissions leave inert rows behind. Sync never deletes, so a slug dropped from the code registry stays in the database and keeps appearing in the picker while gating nothing at all. lens.manage is the live example today - Lens is query-only, its .manage twin guarded no write path and was removed, but the row survives. Granting it has no effect. If a permission is in the picker but not below, that is what you are looking at.

76 permissions in 35 groups.

Products

SlugGrants
products.viewView Products
products.createCreate Products
products.updateUpdate Products
products.deleteDelete Products

Orders

SlugGrants
orders.viewView Orders
orders.updateUpdate Orders
orders.refundIssue refunds and credit memos against any order in scope, including goodwill credits with no return behind them. Money leaves on this permission - grant it separately from orders.update.

Customers

SlugGrants
customers.viewView Customers
customers.manageManage Customers

Admins

SlugGrants
admins.viewView Admins
admins.manageCreate admins, assign roles and edit permission grants. Effectively lets the holder grant themselves anything else, so treat it as the highest privilege short of superuser.

Configuration

SlugGrants
config.viewView Configuration
config.manageManage Channel Configuration
config.manage_defaultsEdit settings at the base scope, which changes them for every channel at once. Distinct from config.manage, which only reaches the selected channel’s overrides.
config.manage_secretsWrite or clear secret-typed settings - gateway API keys, webhook signing secrets, SMTP credentials. Separate from config.manage because repointing where money settles should not ride the same grant as changing a date format.

Catalog

SlugGrants
catalog.viewView Catalog Configuration
catalog.manageManage Catalog Configuration
SlugGrants
search.viewView Search Status
search.manageReindex Search

Developer Tools

SlugGrants
developer.playgroundOpen the GraphQL playground and run introspection against the admin schema. Exposes the full admin API shape, so it belongs with developers rather than operators.
webhooks.viewView Webhooks
webhooks.manageManage Webhooks
remote_providers.viewView Remote Providers
remote_providers.manageManage Remote Providers

Promotions

SlugGrants
promotions.viewView Promotions
promotions.manageManage Promotions

Email Campaigns

SlugGrants
campaigns.viewView Campaigns
campaigns.manageManage Campaigns

Automation Flows

SlugGrants
flows.viewView Flows
flows.manageManage Flows

Reports

SlugGrants
reports.viewView Reports

Email Library

SlugGrants
emails.viewView Email Library
emails.manageManage Email Library

Experiments

SlugGrants
experiments.viewView Experiments
experiments.manageManage Experiments

Notifications

SlugGrants
notifications.viewView Notifications

Web Analytics

SlugGrants
lens.viewView Web Analytics

Checkout

SlugGrants
checkout.viewView Checkout Settings
checkout.manageManage Checkout Settings

Channels

SlugGrants
channels.manageManage channels
channels.viewView channels

Locales

SlugGrants
locales.manageManage locales
locales.viewView locales

Currencies

SlugGrants
currencies.manageManage currencies
currencies.viewView currencies

Countries

SlugGrants
countries.manageManage countries
countries.viewView countries

Media

SlugGrants
media.manageManage media library
media.viewView media library

Inventory

SlugGrants
inventory.manageManage inventory
inventory.viewView inventory

Blog

SlugGrants
blog.viewView Blog
blog.manageManage Blog

CMS

SlugGrants
cms.viewView CMS pages
cms.manageManage CMS pages
cms.types_manageManage CMS page types and attributes
cms.approveApprove CMS pages for publishing

Reviews

SlugGrants
reviews.viewView Reviews
reviews.manageManage Review Questions
reviews.moderateApprove, reject and edit customer reviews before they reach the storefront. Separate from reviews.manage so a moderator can clear the queue without reaching review settings or questions.

Product Q&A

SlugGrants
qna.viewView Product Questions
qna.moderateAnswer / Reject Product Questions

Search Merchandising

SlugGrants
searchmerch.manageManage Synonyms / Pins / Redirects

Forms

SlugGrants
webforms.viewView Forms
webforms.manageManage Forms
webforms.submissions_viewView Form Submissions

Payments

SlugGrants
payments.manageManage payment methods
payments.viewView payment methods

URL rewrites

SlugGrants
urlrewrite.manageManage URL rewrites
urlrewrite.viewView URL rewrites

Import & export

SlugGrants
data.viewSee the transfer hub and its job history - what can actually be run is gated by each resource’s own permission

Cache

SlugGrants
cache.viewView cache settings & activity
cache.managePurge cache & edit cache settings

Scheduled jobs

SlugGrants
scheduler.viewView scheduled jobs
scheduler.manageManage scheduled jobs (schedule, enable, run now, webhook jobs)

Audit log

SlugGrants
audit.viewView the admin audit log