Your store was fast. Category pages served from full-page cache in single-digit milliseconds. Then someone enabled a "small B2B feature" in the admin, and suddenly every category page takes 800ms and the database CPU is pinned. If that feature was Catalog Permissions, you've just met one of the most under-documented performance traps in Magento 2.
Catalog permissions is a legitimate, powerful feature — it lets you control who can view categories, see prices, search, and check out, per customer group. But it comes with three hidden costs that most merchants discover only after the damage is done: full-page cache gets disabled on product and category pages, a permission index starts multiplying rows in the background, and every product collection query becomes heavier. This article walks through all three, how to diagnose them, and what to do when you genuinely need the feature.
What Catalog Permissions Actually Does
The feature (module Magento_CatalogPermissions) is enabled in admin under Stores → Configuration → Catalog → Catalog Permissions. Once turned on you get four grants per customer group and per website, overridable per category:
- Grant Catalog Category View — who can browse a category tree
- Grant Catalog Product Price — who can see prices
- Grant Checkout Items — who can add to cart
- Grant Catalog Search — who gets results in search
The defaults are configured per website and customer group in the admin, and individual categories can override them. On the surface it's clean and flexible. Under the hood it's the closest thing Magento 2 has to a global performance switch you can flip by accident.
Cost #1: Full-Page Cache Stops Working for the Pages That Matter
This is the big one. Once catalog permissions are enabled, product and category content becomes customer-group-dependent: two visitors from different groups can legitimately see different category trees, different products, different prices. The shared, cacheable version of a category page no longer exists.
Technically, Magento handles this via the permission-driven page context — the customer group becomes part of what determines page content, and for the affected pages the response is served as non-cacheable (you'll see Cache-Control: private / no-store in the headers instead of the Varnish-friendly public cache headers). In other words: every category and product page now hits PHP, Magento, and the database on every single request. A store that was doing 2,000 uncached pages per second on a warm Varnish setup suddenly needs the full stack to serve every category view.
The customer data sections mechanism handles small personalized blocks (mini cart, account links) on otherwise cached pages. It is not designed to rescue entire category listings or product pages — those are the page itself, not a block on it. No ESI trick or hole-punching workaround reliably restores caching for permission-gated listing pages; the content genuinely depends on who's asking.
Cost #2: The Permission Index Multiplies Like the Price Index
To avoid joining permission rules into every query at runtime, Magento builds a materialized permission index — think of it as a cousin of the price index: rows are pre-computed per website × customer group × category × product combination.
Do the math. Five customer groups, three websites, 2,000 categories, 100,000 products — even with sparsity (permissions cascade from category defaults, so not every combination gets a row), the index can grow to millions of rows. Every rebuild is a full scan of that space. Reindexing the permission indexers after a mass product or category update is one of those "why is my maintenance window suddenly 40 minutes" moments.
And this index isn't rebuilt once a day in quiet hours — it's an mview-based indexer, updated in near real time as category/products change. That means the growth also produces continuous changelog and indexer load on top of your regular indexers.
Cost #3: Every Collection Query Gets Heavier
With permissions enabled, product and category collections carry an extra permission resolution step. When Magento loads a product collection it must filter it by what the current customer group is allowed to see, and when it renders the category tree it checks every node against the permission matrix.
The typical symptoms:
- Category pages with a normal number of products suddenly generate noticeably more queries and joins, visible in the slow query log and in query counts per request
- The category navigation (the tree in the header/sidebar) gets slower, because every node needs a permission check — on a large category tree this alone can add tens of milliseconds
- Search results are filtered per group too (that's the Grant Catalog Search grant), so search queries carry the same join overhead
- Admin operations that load collections can slow down as well, since the permission logic applies to a lot of shared collection code paths
None of these are catastrophic individually — but stacked on top of a disabled FPC, each uncached request now pays all of them at once. That's the perfect storm: the cache that used to hide the query cost is gone, and the queries themselves got more expensive.
Diagnosing Whether Catalog Permissions Are Your Problem
If your storefront slowed down and you suspect this feature, the checks are quick:
1. Is the feature enabled?
SELECT scope, scope_id, path, value FROM core_config_data
WHERE path LIKE 'catalog/magento_catalogpermissions/%';
An enabled value of 1 means the feature is on. Also check the grants (grant_catalog_category_view, grant_catalog_product_price, grant_checkout_items, grant_catalog_search) — even if enabled, the effective restriction depends on their values.
2. Is FPC actually bypassed?
Fetch a category page with curl -I. A healthy cached response carries X-Magento-Cache-Debug: HIT and public cache headers. A permission-affected page shows private/no-store cache headers — that's the smoking gun.
3. What do the permission indexers look like?
bin/magento indexer:status | grep -i permission
The "Catalog Permissions" indexers (category + product) should be Schedule-managed and up to date. Check catalogpermissions_product_index row count and growth trend in the database — if it's in the tens of millions, the index itself is now a load factor.
4. Correlate the timeline.
Ask when the storefront slowed down and cross-reference with core_config_data timestamps or audit logs. In almost every real case, the slowdown date matches the day someone flipped the feature on.
First Question: Do You Actually Need It?
Before optimizing, question the requirement. A large share of "we need catalog permissions" requests can be solved cheaper:
- "Wholesale customers shouldn't see retail products" → split catalogs across store views or websites instead. Different websites keep full-page cache fully working and give you separate categories, pricing, and even separate layered navigation setups.
- "Wholesale customers pay different prices" → use customer group pricing / tier prices (or catalog price rules). Group-based prices are rendered through customer data sections, so pages stay cacheable while prices adapt per group.
- "We want to hide some categories from the menu" → disable "Include in Menu" per category, or restructure the navigation. Zero permission machinery involved.
- "Only logged-in B2B customers may see our full range" → this is where permissions genuinely shine — but read the next section before committing.
If the requirement is truly view-level gating by group with per-category overrides, catalog permissions is the right tool and you should keep it — just understand the trade-off and budget for it.
If You Must Keep It: The Optimization Playbook
You can't get FPC back for permission-gated pages — accept that. But you can keep the damage contained:
Keep the permission matrix coarse. The index cost grows with the number of customer groups, websites, categories, and products in play. Collapse groups where possible ("Retail", "Wholesale", "Guest" instead of ten micro-segments), and prefer group-level defaults with only a few category overrides instead of per-category rules everywhere.
Watch customer segments and permissions together. Combining both multiplies per-request work: segment conditions evaluate on top of the permission joins. If both are enabled, profile where the time actually goes — often the segments are the larger part.
Manage the permission indexers deliberately. Keep them on Schedule (mview) with sane batch sizes, and align their update windows with your other indexer load instead of letting them fight the main indexers for CPU during business hours. Monitor catalogpermissions_product_index growth — if it trends toward tens of millions of rows, your category/group structure is the problem, not the indexer.
Tune the database for the new query pattern. The permission joins hit the EAV and index tables heavily. Apply the usual medicine: proper composite indexes on the permission tables' foreign keys (website_id, customer_group_id, category_id, product_id), a healthy buffer pool, and query analysis after enabling the feature — the slow query log will tell you exactly which joins need help.
Revisit the search grant. If wholesale needs a separate search experience, check whether both groups really need full-catalog search. Restricting search to a subset changes the search index relevance work, and it's one more place where the permission join runs on every query.
Load test before rolling out. Enable permissions in staging, run your load tests against the uncached permission-gated pages, and measure exactly what "no more FPC" costs you in your traffic mix. Decide whether the business value of the feature justifies the extra PHP-FPM and database capacity — and whether your PHP-FPM tuning can absorb it.
The Bottom Line
Catalog permissions is a dangerous default-on-by-accident feature: it silently disables full-page cache on product and category pages, multiplies an index table, and adds permission joins to nearly every collection — all for the cost of a checkbox. Before enabling it, confirm the business requirement genuinely needs per-group view gating, not just different pricing or menu structure. If it does, keep the permission matrix coarse, manage the indexers deliberately, and load-test the uncached reality.
And if your store already slowed down and you never consciously enabled this feature — check core_config_data for catalog/magento_catalogpermissions, check the cache headers on a category page, and check the permission index size. In the majority of cases, the "mysterious B2B slowdown" is just this one toggle doing exactly what it says on the tin.
Top comments (0)