Skip to content

Staff-Only Collections

A collection can be marked Staff only. Only customers who are signed in and have the Staff role can then see it and buy what is only sold there, so team clothing, for example, can be sold from the main shop rather than from a separate site. Everyone else gets no trace of it: no page, no links, no search results and no sitemap entries.

Setting it up

  1. Open the collection under Catalogue > Collections and turn on Staff only in the Visibility card. The collection list shows a Staff only badge. Collections below it are staff only too.
  2. Give each member of staff the Staff role on their customer record (Customers > customer > Roles). Roles are read when someone signs in, so a newly granted (or removed) role takes effect at their next sign-in.
  3. Link to the collection from the menu. The link only appears for staff.
  4. Admin users, till operators (POS agents) and the shop's MCP tools see and can sell everything, so you can check the shop yourself and sell team kit in person.

Which products are staff only

A product is staff only when every collection it is in is staff only. A product that is also in a public collection stays public: it is simply listed in the staff shop as well (a T-shirt sold to everyone that staff also see in Team Kit). A product in no collection is public. Variants follow their product.

So to make a product staff only, keep it out of every public collection.

What shoppers without the role see

Where What happens
Collection page and its sub-collections, product page, a /c/ category backed by a staff-only collection A guest is sent to sign in and brought back (/login?returnUrl=), so a member of staff can follow a shared link. Anyone signed in without the role gets a 404, as if it didn't exist.
/rest/collection, product page form posts, variant picker, /buy links, /rest/products/sku, event calendar 404
Search, autocomplete, category (/c/) pages and their counts, facet landing counts Staff-only products are left out of every product search. A category whose backing is a staff-only collection is a 404.
Menus, collection choosers, breadcrumbs, Open Graph links No link to a staff-only collection or product
Featured, bundle, product HTML, recently viewed, collection data sources, animation scenes Staff-only products and collections are left out
Trending Staff-only products never trend, for anyone
Sitemap Staff-only products and collections are never listed
REST API (/api/v1/products, /collections, public gallery) Left out, or 404
Basket and checkout Can't be added. A basket still holding one is refused before payment by quick pay and the legacy checkout (checkout.paymentDetails.error.RESTRICTED_ITEMS), and at confirm by the checkout, which releases the card hold so nothing is charged (checkout.msg.restrictedItems). An off-site payment still processing is left to finish and refunded when it succeeds.

A guest being asked to sign in learns only that something sign-in-only lives at that URL, not its name, contents or price; for team clothing that is a fair trade for working shared links. Signed-in shoppers get a 404 because signing in again won't help them.

Guest checkout stays available for everything else. A basket holding a staff-only product can only be paid for by someone signed in with the role.

Keep it out of the merchandising tree

A category backed by a staff-only collection is left out of menu links, product breadcrumbs and the sitemap, and its page asks guests to sign in. But the category rail is built once per site, so the category would still be listed there. Link to the staff-only collection from the menu instead.

The public product gallery API (/api/public/...) carries no sign-in, so staff-only products have no hover gallery even for staff.

How it works

  • Collection.restrictedToRoles holds the roles (the admin switch sets ROLE_STAFF). Products aren't changed.
  • CollectionService.findCollectionAccess(siteContext) returns each restricted collection's roles and the collections it covers (itself and everything below it), plus the unrestricted collections. It is one projected query (and one hierarchy query on a site with restrictions), cached per catalogue in the tiered collectionRestrictions cache and cleared on every collection write. A site with none pays one cache read.
  • ShopperAccessService (commerce-core) answers for the current shopper. Roles come from the login session (BadgerWebAuthTools), never from the cookie-identified User, so a cookie alone grants nothing, and code with no session (worker, sitemap, scheduled jobs) sees everything restricted as hidden. Staff, admins, ROLE_POS_AGENT and ROLE_MCP skip product lookups entirely.
  • CategoryLinkService.route answers HIDDEN for a hidden collection, which covers the collection page, menus, collection choosers, breadcrumbs, social links and the sitemap.
  • Product searches get ShopperAccessService.searchFilter ((collectionSeoNames:!=[hidden] || collectionSeoNames:=[visible])) added in MerchandisingQueryService as each search is sent, and in CategoryCountService, so no listing, count or suggestion can leave it out. The filter lists the visible collections, so a filtered search is sent as a multi_search POST rather than in a query string. A catalogue with thousands of collections would be better served by an indexed per-product visibility field.
  • Cached output that depends on the shopper is keyed with ShopperAccessService.cacheKey: blank for anyone who sees everything, so most shoppers share one entry. The extension model cache and the menu caches (oldMenuNodes, menuHash) use it. Shared caches (trending, facet value counts) leave staff-only products out for everyone.
  • The purchase gates are OrderServiceImpl.addToOrderInternal, UnifiedCheckoutServiceImpl.confirm, ApplePayController and PaymentExtension (before any payment is taken), and OrderServiceImpl.submitOrder as a backstop for every checkout. The worker never places an order from a basket.