Permissions and roles
Every E-commerce permission key, the three separate gates a request passes, the keys that are grantable but wired to nothing, and role recipes that actually work.
E-commerce adds 33 permission keys that something reads, out of 36 the role editor will let you tick. A Super Admin holds everything unconditionally and needs none of them — and is also the only person who can grant them to anyone else. Every other role holds nothing until a Super Admin grants it.
Install and enable says "26 permission keys". That is the subset carried in backend route metadata. There are another 7 that gate the admin screens rather than the API, which is why a role built from the 26 alone lands on a store with no way in.
How a key is shaped
A permission key is the admin route path, with / turned into ., prefixed by
a verb:
| Verb | Gates |
|---|---|
access. |
Opening a screen. Read by the admin menu and by the page gate |
view. |
GET — reading the list, one record, or the option dropdowns |
create. |
POST that creates a record |
edit. |
PUT, including a record's status — which is changed in the row's edit form, not on the row itself |
delete. |
DELETE, single or bulk |
So /admin/ecommerce/product produces access.ecommerce.product,
view.ecommerce.product, create.ecommerce.product, edit.ecommerce.product
and delete.ecommerce.product. There are no manage.* keys in E-commerce.
The three gates
A single click on a store screen is judged in three separate places. They read different keys, and each fails in a way that looks nothing like the other two.
-
The URL gate. Before the page renders, the frontend matches the admin path against a generated manifest and requires the
access.*key listed there. Seven E-commerce paths are in that manifest — product, category, order, review, wishlist, discount, shipping. Any admin path that is not in the manifest falls back to requiringaccess.admin, which is what gates/admin/ecommerce(the dashboard),/admin/ecommerce/settingsand/admin/ecommerce/order/<id>. -
The screen gate. Every store list screen is a data table that carries its own
accessandviewkeys. It issues no request at all unless both pass. -
The API gate. The backend checks the route's own key against the caller's role. A miss answers
Forbidden - You do not have permission to access this— regardless of what the first two gates allowed.
What each half looks like when it is missing
| Granted | What the operator sees |
|---|---|
Only access.ecommerce.product |
The page opens and draws its header, toolbar and analytics — then shows an empty table with "no results". No list request was ever sent. It reads as an empty catalogue, not as a permissions problem |
Only view/create/edit/delete.ecommerce.product |
No link to the store anywhere in the admin nav, and typing the URL bounces you. The menu entry for E-commerce is gated on access.ecommerce.category |
access.* but no access.admin |
The list screens open; the dashboard, the settings screen and the per-order screen do not, and the Analytics tab fails to load on the six tables that have one |
view.* but no edit.* |
The Edit entry in the row's ⋯ menu is still listed, but greyed out with a "You don't have permission to edit items" tooltip, and the Save button in the form is disabled. Nothing on a row can be flipped directly — wherever a store table shows a status, it is a read-only badge |
access.ecommerce.<resource> and view.ecommerce.<resource> are not
alternatives. A working role holds the access. key for every screen it should
open and the verb keys for every action it should take, plus access.admin.
The complete list
Screen keys (7)
These exist only as page gates. No backend route reads them.
| Key | Opens |
|---|---|
access.ecommerce.product |
/admin/ecommerce/product |
access.ecommerce.category |
/admin/ecommerce/category — also the admin menu entry for the whole store |
access.ecommerce.order |
/admin/ecommerce/order |
access.ecommerce.review |
/admin/ecommerce/review |
access.ecommerce.wishlist |
/admin/ecommerce/wishlist |
access.ecommerce.discount |
/admin/ecommerce/discount |
access.ecommerce.shipping |
/admin/ecommerce/shipping |
API keys (26)
These are declared on the backend routes themselves.
| Resource | access |
view |
create |
edit |
delete |
|---|---|---|---|---|---|
| dashboard | access.ecommerce.dashboard |
— | — | — | — |
| product | — | ✓ | ✓ | ✓ | ✓ |
| category | — | ✓ | ✓ | ✓ | ✓ |
| discount | — | ✓ | ✓ | ✓ | ✓ |
| shipping | — | ✓ | ✓ | ✓ | ✓ |
| order | — | ✓ | none | ✓ | ✓ |
| review | — | ✓ | none | ✓ | ✓ |
| wishlist | — | ✓ | none | ✓ | ✓ |
access.ecommerce.dashboard is the odd one out: it is an access. key that
guards an API route (GET /api/admin/ecommerce/dashboard) rather than a screen.
The dashboard screen at /admin/ecommerce is guarded by access.admin, so
both are needed to see figures.
create.ecommerce.order, create.ecommerce.review and
create.ecommerce.wishlist appear in the role editor and can be ticked. There
is no admin create door for any of them — no POST route exists, and no
create button is rendered on those three screens. Orders come from checkout,
reviews and wishlists come from customers.
Granting them is harmless and achieves nothing. That is the gap between the 36 tickboxes and the 33 keys that are read.
Settings is not an E-commerce permission
/admin/ecommerce/settings looks like a store screen and is reached from the
store menu, but it saves through core's platform settings endpoint
(PUT /api/admin/system/settings). That endpoint requires edit.settings.
So a role holding all 33 E-commerce keys cannot change the tax rate, the
shipping fee or the products-per-page value. It can open the screen — the URL
falls through to access.admin — type new values, press Save, and be refused
by the API. Nothing on the screen hides the Save button first.
Grant edit.settings to whoever owns store pricing, and understand that it is
the platform-wide settings permission: it also covers mail, branding and
everything else on /admin/system/settings. There is no store-only settings
permission. The keys themselves are on
Store settings and dashboard.
Keys from outside E-commerce that the store screens need
Three admin screens call endpoints that belong to other parts of the platform. A role without these keys sees the screen work until it hits the missing piece.
| Screen | Also calls | Key needed | Symptom if missing |
|---|---|---|---|
| Product create/edit form | /api/admin/finance/wallet/options |
view.wallet |
The Wallet Type dropdown is empty — you cannot save a product |
| Product create/edit form | /api/admin/finance/currency/options |
view.fiat.currency |
The Currency dropdown is empty for every wallet type |
| Product create/edit form | /api/admin/ecommerce/category/options |
view.ecommerce.category |
The Category dropdown is empty |
| Discount create/edit form | /api/admin/ecommerce/product/options |
view.ecommerce.product |
The Product picker is empty — a discount must name a product |
| Review screen | /api/admin/ecommerce/product/options |
view.ecommerce.product |
The product filter and edit form have nothing to pick |
| The Analytics tab on six of the seven store tables — every one except Categories, which has none | /api/admin/analysis |
access.admin |
The KPI cards and charts fail to load |
The cross-resource ones are the traps. A "catalogue editor" holding only the product keys cannot pick a category; a "promotions" role holding only the discount keys cannot pick a product to discount.
The two money permissions
It is not a fulfilment permission. It covers the status change that moves an
order to CANCELLED or REJECTED, and that transition:
- debits your platform wallet to reverse the store revenue,
- reverses the shipping-and-tax pass-through credit,
- credits the buyer the full original charge,
- restores the stock.
The same key also covers assigning shipments, correcting shipping addresses, and attaching licence keys and download links to somebody's purchase. There is no way to separate "mark this order shipped" from "refund this order" — they are one key. Treat it as a finance permission and give it to the people you would trust with a withdrawal approval.
delete.ecommerce.order is the safer of the two, because the delete doors
refuse an order that has been paid for and not refunded. The request is blocked
by id with an explicit instruction to cancel first. So delete cannot silently
strand a buyer's money — but it can only be used after someone with
edit.ecommerce.order has done the refund. The full behaviour is on
Orders and fulfilment.
Bulk actions carry the same keys as their single-record equivalents. A bulk cancel from the orders table runs the identical refund path, once per order.
Granting them
Permissions attach to roles, and roles attach to users. There is no per-user override.
-
Open the role editor. CRM → Roles & Permissions → User Roles (
/admin/crm/role).access.roleandview.roleopen the screen andedit.rolegets a save past the permission gate — but only a Super Admin can actually save. Every role write door re-checks the caller's role name after the permission check and refuses anyone else: updating a role answersForbidden - Only Super Admins can update roles, saving its permission set answersForbidden - Only Super Admins can sync role permissions, and creating or deleting a role carries the same check. Granting E-commerce permissions cannot be delegated to an admin who is not a Super Admin, no matter which role keys they hold. -
Edit the role you want to change and tick the keys from the recipes below. All 36 E-commerce keys are listed by name.
-
Save. The change is written and the role cache is refreshed.
-
Have the holder sign out and back in. Their session carries the role, and the frontend caches roles and permissions for five minutes.
/admin/crm/permission is read-only — it lists what exists so you can see the
names. Permission rows come from the platform's seed data. If a key you expect
is not listed, it was never seeded, which means no role can hold it and the
route behind it is Super-Admin-only.
Role recipes
Each of these includes both halves of the gate. access.admin is in all of
them because without it the dashboard, the per-order screen and every
Analytics tab are closed.
Read-only reporting
Sees the numbers, changes nothing.
access.admin
access.ecommerce.dashboard
access.ecommerce.order view.ecommerce.order
access.ecommerce.product view.ecommerce.product
access.ecommerce.category view.ecommerce.categoryNo edit. or delete. keys at all, so the Edit and Delete entries in every row
menu are greyed out or absent: no status change, no refund, no destructive
action anywhere in the store.
Catalogue editor
Builds and maintains the catalogue. Cannot see an order or touch money.
access.admin
access.ecommerce.product view/create/edit/delete.ecommerce.product
access.ecommerce.category view/create/edit/delete.ecommerce.category
view.wallet
view.fiat.currencyview.wallet and view.fiat.currency are not optional here — without them the
product form's Wallet Type and Currency dropdowns come back empty and the
product cannot be saved. See
Categories and products.
Fulfilment
Works the order queue and the shipment records.
access.admin
access.ecommerce.order view.ecommerce.order edit.ecommerce.order
access.ecommerce.shipping view/create/edit.ecommerce.shipping
access.ecommerce.product view.ecommerce.productedit.ecommerce.order is what lets them mark an order complete, assign a
shipment and fix an address — and it is the same key that cancels an order and
returns the buyer's money from your platform wallet. If that is not acceptable,
there is no smaller grant that keeps fulfilment working.
create.ecommerce.shipping is there because assigning a shipment links an
existing shipping record to the order; somebody has to create it first. See
Shipping and fulfilment records.
Review moderation
Publishes and unpublishes reviews.
access.admin
access.ecommerce.review view/edit/delete.ecommerce.review
access.ecommerce.product view.ecommerce.productModeration happens in the row's edit form: the Status field there is what
edit.ecommerce.review gates, and it moves a review between Approved and
Pending. The status column on the table is a read-only badge — there is
nothing to click on the row. view.ecommerce.product is needed for the product
filter and the edit form's product field. There is no create.ecommerce.review
door: an operator cannot write a review.
Promotions
Runs discount codes.
access.admin
access.ecommerce.discount view/create/edit/delete.ecommerce.discount
access.ecommerce.product view.ecommerce.productEvery discount names one product, so view.ecommerce.product is required to
create a usable code at all. See Discount codes.
Store manager
Everything except the money door and the settings.
Take the catalogue editor, fulfilment, moderation and promotions sets together,
add access.ecommerce.wishlist with view.ecommerce.wishlist, and decide
separately whether to add edit.settings. Whoever holds edit.settings can
change the tax rate for the whole store, so it is worth keeping on one person.
Diagnosing a permission problem
| Symptom | Almost always |
|---|---|
| No E-commerce entry in the admin menu | Missing access.ecommerce.category |
| Menu entry present, page bounces or shows an unauthorised overlay | Missing access.admin, on a path not in the screen manifest |
| Page opens, table permanently shows "no results" | access.* granted, view.* not — no request is being made |
Forbidden - You do not have permission to access this in the response |
The route's own key is missing from the role |
| A dropdown in a form is empty | The cross-resource key for that dropdown — see the table above |
| Save on the settings screen is refused | Missing edit.settings, which is not an E-commerce key |
| A key you want is not listed in the role editor | It was never seeded; the route behind it is Super-Admin-only |
A permission refusal answers HTTP 403, with the reason — Forbidden - You do not have permission to access this — in the response body as
{message, statusCode}. Both the status code and the body carry it, so either
one is safe to branch on. The endpoint list is on API and data.
Broader failures are on Troubleshooting.