Search by

parkktech / fastmagento

FastMagento makes OpenSearch the serving layer for Magento 2 — products, category pages, search, and layered navigation are served from OpenSearch instead of MySQL, with MySQL kept as the source of truth. Drop-in, base-Magento-only (no Hyvä/Breeze required), cache/Varnish-safe, multi-store, and self

Maintainers

Package info

github.com/parkktech/FastMagento

Type:magento2-module

pkg:composer/parkktech/fastmagento

Transparency log

Statistics

Installs: 30

Dependents: 1

Suggesters: 1

Stars: 9

Open Issues: 1

v2.10.0 2026-09-04 21:29 UTC

README

OpenSearch Serving Layer & Large-Catalog Toolkit for Magento 2
Millisecond pages. Real-time index. Search so good it's almost unfair.

Magento OpenSearch PHP Base Magento only FPC / Varnish safe AI search

Live demo

See it in production → browse, search, and add to cart on DIYoffroad.commillisecond page loads, instant search, and add-to-cart & checkout that paint immediately are all live, real-traffic demos.

Tip

⚡ Fast pages & search here — pair with FastMagento Checkout for instant add-to-cart & checkout

FastMagento makes your catalog pages, category listings and search load in milliseconds by serving them from OpenSearch, and strips the heavy query cost out of the cart. For the instantly-painting add-to-cart & checkout you see on the live demo, pair it with its companion module:

👉 ParkkTech FastMagento Checkout — an instant, cacheable, islands-architecture checkout that paints the moment the HTML lands, keeps every payment gateway working, and fails safe to stock checkout.

composer require parkktech/fastmagento-checkout

🧠 ParkkTech FastMagento Personalisation — an optional companion that builds a per-shopper profile from what they buy, view, search and filter, and re-ranks FastMagento's OpenSearch-served search, listings and recommendation rows for that shopper. Ships dark and off; core runs byte-identically without it.

composer require parkktech/fastmagento-personalization

🐌➡️🚀 Magento 2 is powerful, extensible, and — on a big catalog — perfectly happy to run 20,000 SQL queries to render a homepage and loop your inventory tables thirty times just because you dared to add something to the cart. FastMagento is what happened when we got tired of watching the spinner.

What it is. FastMagento makes OpenSearch the primary serving layer for the Magento 2 storefront. Product data, prices, stock, the category tree, PDP, search and layered navigation are served from OpenSearch instead of Magento's EAV/ORM — while MySQL stays the source of truth and every change is pushed into OpenSearch in real time. Reads stay transparent to third-party code (a real Magento\Catalog\Model\Product / Category object is hydrated from the index), it runs on base Magento only, and it is Full-Page-Cache / Varnish safe.

🚀 Quick install

composer require parkktech/fastmagento
bin/magento module:enable ParkkTech_FastMagento
bin/magento setup:upgrade
bin/magento setup:di:compile        # production mode only
bin/magento cache:flush

# All FOUR indexers. The attribute-option dictionary is what facet labels and swatches are resolved
# from — skip it and every attribute facet silently disappears. setup:upgrade marks all four invalid
# for you, so cron will also build them on its own (on a large catalogue that first cron run IS the
# full build — run this yourself in a screen session instead if you want to watch it).
bin/magento indexer:reindex fastmagento_product fastmagento_category fastmagento_attribute_option fastmagento_review

# Confirm it is actually serving (cluster, indices, indexers, cron, facets, theme, checkout)
bin/magento fastmagento:doctor

Requirements: Magento 2.4.6+ — Open Source or Adobe Commerce (content-staging schema since 2.10.0, see Adobe Commerce compatibility), PHP 8.1–8.5, and a configured OpenSearch/Elasticsearch engine. Full options (VCS install before Packagist, manual install, per-indexer notes) → Installation.

🩺 Something not working? Run bin/magento fastmagento:doctor. Every FastMagento failure mode is silent — the storefront keeps returning HTTP 200 while a feature is quietly off — so the doctor checks each condition explicitly and prints the exact command that fixes it. Add --json for CI, --strict to fail on warnings too.

🎨 Themes. The storefront JavaScript is dependency-free — no jQuery, no RequireJS, no Alpine — so autocomplete and instant search run identically on default Magento (Luma/Blank), Swissup Breeze and Hyvä. None of them is required, and none is special-cased.

🩺 Troubleshooting — bin/magento fastmagento:doctor

Every FastMagento failure mode is silent: the storefront keeps returning HTTP 200 while a feature is quietly off, so there is nothing in the logs a store owner would think to look at. The doctor checks each of those conditions explicitly and prints the exact command or setting that fixes it.

bin/magento fastmagento:doctor            # human-readable report
bin/magento fastmagento:doctor --json     # machine-readable, for CI
bin/magento fastmagento:doctor --strict   # exit non-zero on warnings too
FastMagento Doctor

CLUSTER
  ✓ Cluster health                     engine=opensearch host=localhost:9200 status=yellow nodes=1
  ✓ Index prefix                       "magedemo"

INDICES
  ✓ product serving index              magedemo_products (2046 docs)
  ✓ attribute option dictionary        magedemo_attribute_options (17 docs)
  ✓ Mapping headroom                   26 of 5000 fields mapped

LISTING
  ✓ Category listing source            OpenSearch (falls back to EAV per page on any index miss)
  ✓ Listing collection class           FastMagento collection is wired in
  ✓ User-defined attribute cache       on — attribute metadata is served from the EAV cache

CHECKOUT
  ✓ Fast checkout                      enabled
  ✓ Hyvä checkout compatibility        Hyva_LumaCheckout enabled
  ✓ Checkout theme static content      frontend/Magento/luma deployed for en_US

Run it after every install, upgrade and deploy — most of what it catches is invisible from the storefront, which keeps returning HTTP 200 while the feature is off.

What it covers, and the real failure each check exists for:

Check Catches
Cluster health / connection engine not set, cluster unreachable, RED status
Index prefix the default magento2 prefix shared with another install — two stores silently overwriting each other's indices
Indices + doc counts an index missing or holding far fewer docs than the catalogue (the signature of documents rejected during bulk while the reindex still reported success)
Mapping headroom approaching total_fields.limit, before it starts dropping products
Indexers any of the four missing, invalid, or left on Update on Save
Product URL routing the product index built before 2.8.1 (no request_path keyword field) — product URLs still resolving in MySQL until the next reindex
Review index missing or empty while approved reviews exist
Cron no recent indexer_update_all_views success — scheduled indexes silently going stale
Facets configured attributes that Magento will not aggregate, and an unbuilt option dictionary (which drops every attribute facet)
Listing whether the PLP is served from OpenSearch, and whether another module has re-pointed the collection virtual type
User-defined attribute cache dev/caching/cache_user_defined_attributes left off (Magento's default) — two DB queries per filterable attribute on every listing render, warm cache included. On a 21-attribute catalogue that was 41 of 81 warm queries
Theme the active theme per store view
Checkout fast checkout enabled, and the Hyvä Luma-checkout fallback when Hyvä is active
Checkout theme static content the theme Hyva_ThemeFallback swaps to for /checkout/index has no deployed CSS — checkout returns HTTP 200 with every asset 404ing underneath, RequireJS included, so the Knockout checkout never boots
PHP memory_limit, max_execution_time

Exit codes are CI-friendly: 0 clean, 1 if any check failed (or, with --strict, warned).

Why it exists (the honest origin story)

It started as one simple goal: make Magento faster. Then we tried to run a real store on it — a lingerie catalogue with 50,000+ colour and colour-combination options and configurable products with 7,500+ variations each — and Magento, bless it, fell over. There had to be a way to serve that data fast and efficiently. This extension is that way.

So a "make it faster" project quietly became "make Magento handle huge catalogs", and along the way it grew into a set of standalone features — each one solving a real large-catalog pain point on its own (see the feature sections below). Adopt the whole serving layer, or just the one piece you came here for.

Checkout is now lightning fast. Native Magento loops the inventory tables ~30 times just to add a product to the cart — then loops them again when the cart page loads. FastMagento strips those redundancies and offloads the heavy database work to OpenSearch, which indexes updates instantly and serves even very complex configurable pages in milliseconds. At that point the only real overhead left is Magento bootstrapping itself.

🔗 See it in action: www.diyoffroad.com — a live storefront running this serving layer.

🎬 See the magic (live demos)

Watch it happen — no page reloads, no spinners, on a catalog this size. (GIFs recorded from the live storefront; drop-in replacements at the paths below.)

As-you-type autocomplete search from OpenSearch
⌨️ Autocomplete
Products + categories appear as you type, in milliseconds.
Instant search results page updating live with no reload
⚡ Instant SERP
The whole results grid re-renders live — zero page reloads.
Shop By layered navigation updating in real time
🎛️ Live "Shop By"
Tick a filter → grid, counts & facets update instantly.

🎸 And here's the kicker

All of this is pure backend performance — no Hyvä, no Swissup Breeze, no theme surgery required. FastMagento makes Magento serve data in milliseconds on the stock Luma frontend. Now pair it with a fast frontend theme like Hyvä or Swissup Breeze, and you've got a genuine banger of a site — lightning back end and front end. They optimize the paint; we obliterate the data cost underneath it. 🤝

Problems it solves (problem → solution)

ℹ️ Scope today: category listings (PLPs) are still served from MySQL

Worth stating plainly, because it is the single most common "is it broken?" question. FastMagento serves PDPs, search, autocomplete, the category tree/menu and the cart from OpenSearch. It does not yet serve the product listing on a category page — that path ships disabled in view/frontend/layout/catalog_category_view.xml while the block/template contract is finished (Phase 2 / 2L of the serving-layer plan).

So a category page still runs Magento's normal EAV listing queries — on a page of configurables that is roughly 7 queries per product (tier price, catalog rule, media gallery, stock, super link/attribute). Measured on a stock 2.4.7-p10 + Luma-sample-data install: ~360 SQL on a 12-configurable category page cold, of which ~158 touch catalog_product_entity. That number is the same with the module enabled or disabled, on every Magento version — it is not a regression and not a theme problem. PDPs on the same install: 5–13 product queries. Autocomplete: 0.

Every one of these is a real Magento 2 pain on a large catalog — and a standalone reason to install:

  • Problem: a homepage/PDP fires thousands of EAV queries and cold pages crawl. Solution: products are served from OpenSearch as real product objects → 0 product/EAV SQL on the hot path.
  • Problem: adding to cart loops the inventory tables ~30×, and the cart page loops them again. Solution: Fast Checkout serves lines from the index and drops the redundant stock revalidation → cart render flat (~4 s → ~0.4 s on a 7-item cart), can't oversell.
  • Problem: a configurable with thousands of variants melts PDP/cart with a per-child price/stock N+1. Solution: per-child price/stock/rule data is pre-indexed → the ~660/7,500-child N+1 becomes 0 price SQL.
  • Problem: the category menu, breadcrumbs and layered nav fan out into catalog_category_entity_* UNIONs and a per-category URL-rewrite N+1. Solution: the whole tree is served from one OpenSearch read → 0 category EAV reads, URL-rewrites batched to one query.
  • Problem: Magento search is slow, reloads the whole page to filter, and ranks poorly. Solution: instant autocomplete + a live SERP with live layered navigation (no reload) + AI-tuned relevance.
  • Problem: an attribute with 50k options (colors) makes the admin attribute page hang or crash. Solution: a paginated option manager — one page at a time, search by name/ID, single-row edit/delete.
  • Problem: the frontend and the admin fight over the same database, so the back office is slow under load. Solution: the heavy read path moves to OpenSearch → the admin gets faster on the same hardware.
  • Problem: going fast usually means core patches or ripping out third-party modules. Solution: base Magento only, real product objects, FPC/Varnish-safe, every feature toggle-able → drop-in.

Built for large, attribute-heavy, complex-configurable catalogs

This extension targets the catalogs where native Magento hurts the most — many attributes and configurables with hundreds of variants — and it is architected so per-page product/EAV cost stays flat as the catalog grows. Every PDP, PLP and search request resolves to a bounded OpenSearch read (one search + one mget), never a scan of the catalogue, so page latency does not climb with product count the way native EAV hydration does.

Capacity. Because the per-request cost is independent of catalog size, the practical ceiling is your OpenSearch cluster, not Magento's EAV. OpenSearch comfortably indexes millions of documents on modest hardware, and the serving reads (bounded search + mget by id) stay constant-time regardless of how large the catalogue gets. In practice this is designed to serve catalogues from a few thousand to 1M+ products without per-page performance degradation; beyond that, OpenSearch sharding scales further. The one operation that grows with catalog size — reindexing — streams documents in bulk chunks for flat memory use, so it scales too.

Complex configurables. Native Magento prices, hydrates and stock-checks a configurable by iterating every child, so cost scales with variant count — a configurable with hundreds of variants is a worst case that makes cold PDP, cart and checkout crawl. The serving layer turns those into flat index reads, and is validated against configurables carrying several hundred variants across multiple axes. If your catalogue is large, attribute-heavy, or full of big configurables, this is exactly the workload it targets; a small, simple catalogue still gets the query reductions but feels less wall-clock benefit (see the note under Benchmarks).

Measured on a production-sized catalog:

Page Before After
Cold homepage ~21,610 SQL ~1,084 SQL
PDP (any type) hundreds of EAV queries 0 product/EAV SQL
Category page ~58+ category/EAV queries 0 product/EAV SQL
Search results 342 SQL 118 SQL

📊 Performance at scale — 500,000 products (with vs without)

Measured on a purpose-built 500,000-product stress catalog (513k simple products across 4,187 configurables, a 50,000-option color attribute, every bra/apparel size) in an isolated database. Apples-to-apples: the module is fully disabled (module:disable, real native MySQL) vs enabled, replaying the same URLs and the same 10-configured-item cart. Cold render (every request a unique URL → Full-Page-Cache miss, median of 3) — the honest comparison, because on an FPC hit both states serve identical cached HTML. Cart/checkout use the REST totals endpoints (never FPC-cached). The store runs its real third-party stack (Webkul Marketplace, Stripe, …) on every page in both columns.

Page load / render time @ 500k products (cold render)

Surface Without (native) With FastMagento Speed-up
Home / CMS 2,911 ms 775 ms 3.8×
PDP · simple 555 ms 480 ms 1.2×
PDP · configurable (660-variant) 1,371 ms 827 ms 1.7×
Category / PLP 543 ms 622 ms ~1×
Search results (SERP) 631 ms 338 ms 1.9×
Cart · collectTotals (10 configured) 1,485 ms 1,009 ms 1.5×
Checkout · totals-information 1,510 ms 1,110 ms 1.4×

SQL queries per cold render @ 500k products — the scale-invariant metric:

Surface Without (native) With FastMagento Reduction
Home / CMS 10,090 80 −99%
PDP · simple 413 256 −38%
PDP · configurable 388 231 −40%
Category / PLP 246 131 −47%
Search results 220 23 −90%
Cart · collectTotals (10 configured) 1,210 265 −78%
Checkout · totals-information 1,250 295 −76%

How to read it (honestly). The query-count collapse is the headline — the homepage alone goes 10,090 → 80 queries (~126×), and the never-cached cart/checkout path drops ~4–5× (1,210 → 265). Wall-clock wins are biggest where data-loading is the bottleneck (home, search, configurable PDP, cart); it's roughly flat on light pages (simple PDP) and can even be a hair slower on a small category, because there the time is dominated by PHP rendering + third-party modules (Webkul Marketplace alone fires ~180 queries/PDP) — identical in both columns — and the OpenSearch round-trip adds a few ms. On a production DB (millions of EAV rows, 1–5 ms/query, networked/replicated) that same query collapse is seconds of latency and a large drop in DB load. The design goal — product/EAV SQL stays flat as the catalog grows — holds; native does not. These page-time figures are server response (TTFB-class); the real browser gap is wider, because native Magento also ships a heavier DOM (more blocks, more inline data) that costs the browser more to parse and render — a full-page-load (DOMContentLoaded / fully-loaded) comparison amplifies the difference beyond what server time alone shows.

Cached (FPC hit) @ 500k — the returning-user experience:

Surface With or without FastMagento
Any FPC-cached page (home, PDP, category) ~231 ms (identical — served from cache)
Search results (not FPC-cached) ~665 ms

On an FPC hit both states are identical (~231 ms) — the extension's win is on cache miss (first views, high-churn catalogs) and the never-cached paths: search, cart, checkout.

Admin @ 500k — the 50,000-option attribute:

Metric Without (native) With FastMagento
Attribute-edit page (50k-option color) crashes / hangs opens instantly (paginated, 50/page)

Paginated attribute-option manager on a 50,000-option attribute

🔎 Extension Efficiency Monitor — find which extension is slowing you down

Third-party extensions don't advertise their cost. FastMagento's Extension Efficiency Monitor (Admin → FastMagento → Extension Efficiency) profiles a real page render — data loads, full PDP / category / search HTML, and the checkout totals path — then attributes every SQL query to the extension that fired it. It answers the question every store owner actually has: "which app is making my store slow, and where do I tell my developer to look?"

The headline is the N+1 hotspot table — a developer can read one row and know exactly where to go: Extension · Page · Class::Method · Loops · Table. On this 500k-product store with its real third-party stack it surfaces, automatically:

Extension Page Class :: Method Loops Table
Webkul Marketplace PDP Data::getSellerCollectionObj ×87 marketplace_userdata
Webkul Marketplace PDP SellerIdAttribute::getAllOptions ×84 marketplace_userdata
Mageplaza Productslider Home AbstractSlider::getProductParentIds ×11 sales_bestsellers…
Jadog StructuredData PDP BreadcrumbSchema::bestCategory ×9 catalog_category_entity
StripeIntegration Payments Checkout Product::getProduct ×4 catalog_product_entity

Plus at-a-glance customer-experience load times per page (Home / PDP / PLP / Search / Checkout, colour-coded), a per-hot-path core-vs-extension query "tax" chart, and a severity-ranked table of every extension's total database overhead. Run it on demand, on a schedule (cron), or from the CLI (bin/magento fastmagento:efficiency:scan). It works for any extension — no per-extension config.

Extension Efficiency Monitor — N+1 hotspots, load times, and per-extension database overhead

The reference numbers below are from a mid-sized (~14.6k-product) catalog; the 500k tables above are the headline "does it hold at scale" proof.

Benchmarks — with vs without the extension

Measured on this storefront by toggling ParkkTech_FastMagento on and off and replaying the same requests. Cold render = Full-Page-Cache disabled — the real product-render path (an FPC hit renders no product blocks, so it hides the cost). Environment: local dev, a mid-sized catalogue with active catalog price rules across customer groups and configurables up to several hundred variants, with Webkul Marketplace and other third-party modules running on every page.

Cold-render SQL queries — native Magento vs FastMagento

SQL queries per cold render

Surface Product type Without (native) With FastMagento Reduction
PDP Simple 822 540 −34%
PDP Downloadable 784 512 −35%
PDP Configurable (660 variants) 774 494 −36%
PLP Category listing 452 224 −50%
Search Results page 436 48 −89%
Cart / Checkout Configurable + simple 524 252 −52%

Why wall-clock isn't quoted here. On a local-dev box MySQL answers each query in ~0.1 ms, so removing a few hundred queries only saves tens of milliseconds — swamped by PHP and the third-party modules that run on every page (Webkul Marketplace alone fires ~180 queries/PDP, identical with the extension on or off). Local wall-clock therefore understates the benefit, so we don't present it as a headline number. The query-count reduction above is the scale-invariant metric: the reads FastMagento removes are the product / EAV / catalog-rule queries whose cost grows with catalog size and DB round-trip latency. On a production store — millions of EAV rows, a networked / replicated database at 1–5 ms per query — the same reduction becomes seconds of latency and a large drop in DB load. The design goal: product / EAV SQL stays flat as the catalog grows; the native path does not. (Scale-tested wall-clock at 500k products appears earlier in this README.)

Cart / checkout is the clearest real-world win: wall-clock there is dominated by configurable line-item hydration and grows with basket size. With the default-on Fast Checkout, a realistic 7-item configurable cart goes from ~4 s to ~0.4 s (~10×) — see the Fast Checkout table below.

N+1 query patterns eliminated

The patterns that make checkout and large-catalog pages fall over:

  • Configurable price resolution. Native Magento prices a configurable by iterating every child (660 here) through the price model; on the serving-layer path this produced one catalogrule_product_price and one catalog_product_entity_tier_price query per child. Child prices (per customer group) are now served from the index → 0 rule/tier SQL on the configurable PDP.
  • Cart / checkout reprojection. Writing stock for a single configurable child reprojected the whole parent inline, calling getRulePrice() once per child (~660 queries) on the revenue path. The indexed base price is now rule-neutral (no per-child price observer) and reprojection is deferred off the response and skipped when stock is unchanged → 671 → 8 catalog-rule queries on a cart view, and the shopper never waits on the reindex.
  • Search. Relevance + layered-nav facets served from the index → 436 → 48 SQL.

Catalog price rules are resolved per customer group (guest, Wholesale, Retailer, …) across PDP, PLP, cart and checkout, served from a per-group map in the index rather than a per-child SQL lookup.

Fast Checkout — configurable-cart render time

The cart/checkout HTML render cost is dominated by hydrating each configurable line item (parent + used-products + per-source MSI salability), so native checkout time grows with the number of configurable line items. Fast Checkout (Enable Fast Checkout, on by default) serves those line products from the index and flattens it. Warm render, this storefront:

Cart Native Fast Checkout
1 simple 0.93 s 0.41 s
1 configurable 1.08 s 0.42 s
7-product cart (real-world mix) ~4.0 s ~0.4 s
10 configurable variants 3.34 s 0.41 s

Native cart/checkout render grows with every configurable line (~230 ms each), so a realistic basket compounds fast — a 7-product cart takes ~4 s natively. Fast Checkout is flat at ≈0.4 s regardless of cart size or configurable count, so that same 7-item cart renders in ~0.4 s — roughly a 10× cut. Order placement still re-checks salable quantity by SKU (MSI reservations), so it cannot oversell; any product missing/partial in the index falls back to the native path automatically.

Features at a glance

Every capability below is a standalone feature — run the whole serving layer, or just the one you came for:

Feature What it fixes
📦 OpenSearch product serving (PHP loads from OS) 0 product/EAV SQL on PDP, cart, listings
🗂️ Category & navigation serving 0 category EAV reads for menu / breadcrumbs / nav / the current category itself
🧭 URL routing from the index url_rewrite lookups on every request
Product reviews from the index review list + per-review rating N+1 on every PDP
🧱 Category widgets & sliders home-page "featured/new" collections loaded from EAV
🧊 Render-time caches menu ESI round-trip under Varnish, layered-nav / section-data / rating-set / attribute-list renders
🔗 Related, up-sell, cross-sell & sliders per-item EAV + URL-rewrite N+1
🧩 All product types served simple, configurable, downloadable, virtual
⌨️ Autocomplete search (as-you-type) slow native autocomplete
Instant search results page (live SERP) full page reload to search
🎛️ Live layered navigation on search full reload to filter / "Shop By"
🤖 AI-powered search relevance & keywords poor Magento relevance on big catalogs
🛒 Fast Checkout 30× inventory loops on add-to-cart / cart
🔄 Real-time stock & price sync stale index after orders / rule changes
💲 Per-customer-group & B2B pricing per-group price N+1
🎨 Paginated attribute options (50k+) — filter & bulk-clean unused attribute page crashing at 50k+ options; thousands of unused options
🏷️ Option labels served from OpenSearch residual eav_attribute_option reads on PDP / PLP / search
🏎️ Faster admin on the same hardware admin slow while frontend hits the DB
🛟 Read-path resilience & fallback OpenSearch outage safety
🔌 Drop-in & third-party transparent going fast without core patches

Feature: OpenSearch product serving (zero EAV reads)

Zero. That's how many product/EAV queries your PDP fires now. Not fewerzero. Go ahead, count them. We'll wait. 😏

Every product read on the storefront — PDP, cart, related/up-sell, search hydration, product sliders — is answered by a ShellNoEavProduct: a real Magento\Catalog\Model\Product subclass whose load() is a no-op and whose getters return from the indexed _source. This removes the per-product catalog_product_entity_{varchar,int,decimal,text,datetime} EAV multi-table load and its option-label lookups, and serves price, special/tier/catalog-rule prices (per customer group), stock, media gallery, category names, URLs and every custom attribute — with select/multiselect values pre-resolved to labels in the index. Because it stays a real product object, third-party blocks, plugins and SEO modules keep working unchanged.

Feature: Category & navigation serving

Your mega-menu used to cost 100+ queries every single page load. Now the entire category tree arrives in one search and lives in memory. Breadcrumbs, nav, "Shop By" parents — all free.

A dedicated fastmagento_category indexer (magento2_categories) projects the whole tree; a request-scoped provider pulls it in one search and answers the mega-menu, top navigation, breadcrumbs and layered-navigation category data from memory — eliminating the catalog_category_entity_{varchar,int,text} UNION attribute loads (0 category EAV reads). A batched URL finder collapses the per-category url_rewrite N+1 (~108 queries → one per store).

Since 2.8.1 the current category itself comes from the same document on the storefront: CategoryRepository::get() (the category controller and the layer), the breadcrumb walk (getParentCategories), the design walk (getParentDesignCategory) and the child list (getChildrenCategories) are built from the indexed document — the entity load, the exists-check, two EAV collection loads and a url_rewrite join per category and search page. Storefront only; admin, API and GraphQL keep the database, and a category the index does not hold falls through. The category page's product grid is served from the index as well (listing hydration, 2.x).

Under Varnish, Magento turns the menu block into an ESI include, which costs a second Magento bootstrap, routing pass and web-server hop on every cache miss. FastMagento renders the menu inline instead (block names configurable; defaults cover Luma/Breeze catalog.topnav and Hyvä topmenu_generic), so a miss is one backend request. Serving → Render The Category Menu Inline.

Feature: URL routing from the index

The URL-rewrite router's request-path lookup — two or three url_rewrite queries on every request — is answered from the category tree or one term query on the product index (request_path is a keyword field since 2.8.1; reindex fastmagento_product once). Only current canonical paths are answered: old paths that 301, CMS pages, product-in-category paths and custom rewrites still come from url_rewrite, so redirects keep working. The router's follow-up lookup of the resolved system path is answered without a query. Serving → Resolve Product And Category URLs From The Index.

Feature: Product reviews from the index

A dedicated fastmagento_review indexer (2.8.0) projects every approved review, with its rating votes, into its own index — one document per review, so a popular product's thousand reviews never ride along on the product document, and a review save touches one document plus the two summary fields on its product (which also keeps reviews_count / rating_summary on product cards current; before 2.8.0 no mview subscription covered the review tables). The product page's review list, its pager total and every review's stars come from one search instead of a COUNT, a SELECT and one rating_option_vote query per review shown — on Hyvä, Luma and Breeze (whose list loads through review/product/listAjax) alike. The review form's rating set is cached per store, invalidated on rating save. Net: a product page runs no review or rating query at all. The full build streams the review table by keyset in fixed pages, so memory is flat at any review count. Serving → Serve Product Reviews From The Index.

Feature: Render-time caches

Once the data comes from the index, what an uncached page still costs is template time. Four blocks whose output does not vary per visitor are block-cached with the right keys (2.8.0–2.8.1): the layered navigation HTML (per category / search query, applied filters, store, currency and customer group — filters are still applied to the product collection, only the rendering is cached), Hyvä's default-section-data script (per store and currency), the review form's rating set and the layer's filterable-attribute list (both per store, cleaned on attribute / rating save). Each has its own Serving toggle and lifetime where relevant.

Feature: Related, up-sell, cross-sell & product sliders

Link blocks and slider widgets hydrate from OpenSearch (one link-graph query + one mget), and child→parent lookups resolve from the indexed parent_ids — removing the per-item EAV load, the per-card url_rewrite N+1, and the catalog_product_super_link parent N+1 that make product sliders quietly expensive on every page.

On a product page that is the difference between 20 and 3 product queries cold (2 warm): the related block is the single most expensive thing left on a PDP once the product itself comes from the index.

Category widgets and sliders (2.9.0). "Products of category N" collections — CMS product widgets, Hyvä product sliders, featured/new home-page blocks — are plain product collections the listing hydrator never saw. A strict recogniser reads the collection's SELECT (one catalog_category_product_index join, visibility list, optional is_parent, the standard website/stock/review/price joins, an optional base-price range, position / price / no sort, a page size) and fills the collection from the index instead of the e.* load plus the EAV attribute passes. Two id sources: Database (default) takes the ids from one index-only query built from the widget's own SQL, so the page is byte-identical to native — including the order MySQL happens to pick among products with the same price or position, which nothing else can reproduce; Search index takes them from Magento's own search index too (no MySQL for the widget at all; ties ordered by product id). Anything outside the shape stays native. Serving → Serve Category Widgets And Sliders From The Index / Widget Product Ids Come From.

Feature: All product types served

  • Simple & Virtual — fully served.
  • Downloadable — links and samples are indexed and hydrated into the native downloadable blocks (title, price, per-link samples), rendered with 0 downloadable SQL.
  • Configurable — swatch jsonConfig, per-option prices and swatch config are served from OpenSearch; the PDP renders the full swatch UI, even for configurables with thousands of variants. Out-of-stock option combinations still render greyed, matching Magento's default.
  • Grouped / Bundle — indexed; add-to-cart hardening in progress.

Feature: Autocomplete search (as-you-type dropdown)

Blink and you'll miss it. The shopper starts typing — and the right products are already sitting there, images, prices and all. Faster than they can finish the word.

An Algolia-style autocomplete dropdown on the header search box: as the shopper types, a debounced AJAX call returns product cards (image, name, price, in-stock) and matching category suggestions, rendered instantly with keyboard navigation. Results come from OpenSearch — relevance from the analyzed fulltext index, display data hydrated from the serving index in one mget — so suggestions appear in milliseconds and every keystroke aborts the previous request. Endpoint: /fastmagento/search/suggest.

Feature: Instant search results page (live SERP, no reload)

A search results page that never reloads. On Magento. Read that again. We'll give you a second.

The search results page is a fully live SERP: the product grid and pagination re-render in place from OpenSearch with no page reload as the shopper types or pages, and the URL updates via history.replaceState (shareable, back-button friendly). It replaces Magento's native server-rendered search results outright and returns hits in milliseconds. Endpoint: /fastmagento/search/instant.

Feature: Live layered navigation on search (instant "Shop By")

Tick a filter. Watch the entire page rearrange itself. No reload. Facet counts, grid, pagination — all instant. Your customers will think you rebuilt the store in React.

The layered-navigation "Shop By" facets on the search page update live — tick a filter and the grid, pagination and facet counts all re-render in place from OpenSearch aggregations, no page reload. Facet option labels come straight from the index (no DB/EAV lookup on the request path). Configure which attributes become facets via FastMagento > Search > Facet Attributes.

They look like the rest of your storefront, because they are your storefront's markup. Only the native product list is replaced; layered navigation stays, so the theme renders its own wrapper, heading, collapsible groups and mobile toggle — and the storefront JS refills those groups from the OpenSearch aggregation. No theme class is hardcoded and the module ships no styling for facets, so Hyvä, Luma and Breeze each get their own look with nothing to re-skin.

The data is worth the arrangement: on one search page native layered nav aggregated no size filter, one colour and two categories, where the OpenSearch aggregation returned 5 / 11 / 14.

Applied filters are visible and removable through the theme's own "currently filtering by" block, rendered via Magento_LayeredNavigation::layer/state.phtml through the theme fallback — again, the theme's chips, remove buttons and "Clear All". Every link it renders is a real results URL for the set it leads to, so removing one option, removing the last, and clearing everything are one code path, and they still work with JavaScript off.

Feature: Fast Checkout — no more 30× inventory loops

Imagine a Magento where your customers aren't punished for adding more to their cart — or for you offering richer sales-rule flexibility. Our cart paints the DOM in under a second whether there's 1 item or 20 highly-configured products — something natively impossible in Magento without slowing checkout to a crawl.

Here's why native Magento does punish a full cart: it hydrates each line from a ~217-query product collection (EAV ≈119 + MSI stock ≈71 + downloadable ≈27) and re-checks salable quantity ~30 times per line on add-to-cart and again on cart load — so every extra item, every extra configurable variant, and every extra sales/catalog rule piles on more work. Cost grows with the size and complexity of the cart, exactly when the shopper is closest to buying.

Fast Checkout (on by default) serves those line products — including configurable variants — from the index and drops the redundant revalidation, so cart/checkout render is flat regardless of cart size (a realistic 7-item cart goes from ~4 s to ~0.4 s; a 20-line highly-configured cart stays under a second). One master toggle turns on the whole fast pipeline (OS-serve + optimistic stock + fast stock sync); order placement still gates stock by SKU so it cannot oversell, and anything not fully in the index falls back to native automatically.

Feature: Real-time stock & price sync

Sell it, refund it, restock it — the index already knows. Updates land in OpenSearch before the response even finishes, so your storefront is never stale and the shopper never waits.

Order placement, refunds/returns and MSI inventory-API writes reproject the affected products (and their configurable/grouped/bundle parents) into the index immediately — after fastcgi_finish_request, so the shopper never waits — keeping quantity and in-stock status live between reindexes. Fast stock sync patches only the stock fields via one mget + bulk update instead of a full EAV reprojection. Catalog-rule recalculations (rule save / nightly apply-all) patch per-group rule prices into the docs automatically, so the OS-served cart is correct with no manual reindex.

Feature: Paginated attribute options — manage 50,000+ options without crashing

The one screen that made Magento give up. 50,000 color options used to hang the attribute page. Now it opens instantly, you search by name or ID, and every edit saves a single row. The impossible admin page — solved.

Keywords: Magento paginated attribute options, attribute option pagination, manage thousands of swatch options, 50k color attribute. A well-known Magento 2 shortcoming: the product-attribute Manage Options grid (and the swatch variants) renders and re-serialises the entire option set on one page. Past a few thousand options the attribute-edit page slows to a crawl or crashes outright — a hard blocker for large apparel/lingerie catalogs where a single color attribute can carry tens of thousands of combinations.

FastMagento replaces that screen with a paginated option manager:

  • Loads one page at a time (AJAX) — the edit page opens instantly even at 50,000 options.
  • AJAX search by name or ID — find one specific option among 50k to edit or delete.
  • Single-row writes — add / edit / delete is one AJAX call touching only that option's rows; the whole set is never rewritten. A guard plugin stops the native "save the whole array" path from ever running, so a Save Attribute click can't wipe your options.
  • Every option type — dropdown, multiple-select, visual swatch (color/image), text swatch.
  • "Assigned to a product" column + filter — see at a glance which options are actually used, and filter to Any / Yes / No. On the 50k color attribute this instantly surfaces the 23,201 unused options.
  • Bulk delete — check rows and delete the selection, or "Delete All Matching" to remove the entire filtered set across all pages in one action (chunked). Cleaning out thousands of dead options — filter → Unassigned → delete — is one click; the confirm warns if any are still in use.

On by default. See Admin: paginated attribute options for details.

Feature: Option labels served from OpenSearch (no MySQL on the hot path)

Every select/multiselect option label (PDP "Additional Information", layered-nav Shop By facets, swatches, search) is projected into an OpenSearch option dictionary and served from it, so a served page resolves labels with zero eav_attribute_option MySQL reads — dropping the residual per-page option lookups to 0 on PDP, PLP and search. Native fallback on any miss.

Feature: Fast, non-blocking reindex

Reindex a 500k catalog in minutes, and never take the store down doing it.

The product indexer is set-based and batched: it loads each chunk of products in one pass instead of one getById() per product, so it doesn't drag every installed third-party module through a per-product load (a single mis-behaving extension can otherwise add millions of wasted queries to a reindex) and it builds each OpenSearch document from explicit batched queries. On a 500,000-product catalog this cut a full reindex from ~2.6 hours to ~50 minutes (≈3× overall, and ~8× on the simple-product volume) — with no extra hardware and no parallelization (which would just fight the app server for CPU); it's simply less work per product.

Products never disappear while it's reindexing. Search and product pages keep serving throughout a rebuild: any product not yet in the (rebuilding) serving index is loaded natively once and read-through-indexed on the spot (warm-on-miss), so a shopper never sees an empty results grid or a missing PDP mid-reindex — the store stays fully live from the first second of the rebuild to the last.

Feature: Read-path resilience & fallback

Warm-on-miss (a product missing from the index is loaded natively once, projected, then served from OpenSearch thereafter — self-healing like a cache miss) and automatic native fallback whenever OpenSearch is unavailable. Serving happens beneath Full-Page-Cache and Varnish, so it is cache-transparent.

Feature: Faster admin on the same hardware

But wait… there's more! 📺 Move the storefront off MySQL and your admin gets faster too — imports, indexers, order processing, all of it — on the exact same hardware. That's not an add-on. That's a free bonus you didn't even ask for.

A quiet but real win: because the frontend no longer hammers MySQL for product/EAV/price/stock reads (that work now lands on OpenSearch), the database and PHP-FPM pool spend far less time serving storefront traffic — so the Magento admin, imports, indexers and integrations get noticeably faster on the exact same hardware. The frontend and the back office stop fighting over the same database. No extra servers, no config: it's a side effect of moving the heavy read path off MySQL.

Feature: Per-customer-group & B2B pricing served from the index

Catalog price rules, tier prices and special prices are indexed per customer group and served straight from the document on PDP, PLP, cart and checkout — so a store with many customer groups (guest, wholesale, retailer, VIP…) resolves the right price with no per-child, per-group SQL lookup. This is what kills the classic per-child catalogrule_product_price + tier-price N+1 on a big configurable, and keeps B2B pricing fast at scale.

Feature: Drop-in & third-party transparent (base Magento only)

No core hacks. No ripped-out modules. And no Hyvä or Breeze required. It's all backend — pair it with a fast theme for a real banger, or run it on stock Luma and still fly. Turn any piece off and it falls straight back to native. Your extensions never even notice.

FastMagento runs on base Magento — no core patches. Because every read returns a real Magento\Catalog\Model\Product / Category object (just hydrated from OpenSearch instead of the DB), third-party extensions, SEO modules, themes and page builders keep working unchanged. It sits beneath Full-Page-Cache / Varnish, and any feature can be toggled off to fall straight back to native behaviour. Adopt it incrementally.

Hyvä, Luma and Breeze from one bundle

The bulk of FastMagento is backend, so it's theme-agnostic. Its storefront JavaScript — the as-you-type autocomplete and the live instant-search results page — has no dependencies: no jQuery, no RequireJS, no Alpine, no Knockout. It loads as a plain deferred <script> and boots from application/json islands rendered by the module's own templates, so the same build runs unchanged on every theme. (It used to be jQuery bootstrapped through text/x-magento-init, which only Magento's RequireJS bootstrap executes — on a theme that ships no RequireJS the markup rendered and nothing ever read it.)

Verified end-to-end on the same store, switching only the active theme — listing, product page swatch selection, add-to-cart and checkout on each:

Storefront Listing PDP swatches Add to cart Checkout Warm listing queries
Hyvä 1.5.2 32
Luma 35
Breeze Blank 2.10 33

One product query per 12-product grid on all three. One codebase, three stacks.

What it serves from OpenSearch — and the SQL it removes

On the storefront hot path, product and category reads resolve from an OpenSearch document instead of Magento's EAV/ORM. Each mechanism replaces a specific native query pattern — the ones whose cost grows with catalog size, attribute count and configurable variant count. The read path is intercepted at the single Magento\Catalog\Model\Product::load choke point (and the product repository, product/link collections, category collection, price models, stock registry and the quote-item collection), so third-party code keeps receiving a real product/category object. (The write path still runs set-based SQL at index time; the elimination is on the read/serving path.)

Serving mechanism What it answers from the index Native SQL it removes
ShellNoEavProduct — no-op load(), getters from _source Every product attribute read on PDP / cart / listing catalog_product_entity_{varchar,int,decimal,text,datetime} EAV joins + option-label lookups
Per-customer-group price maps on the doc + TierPrice/CatalogRulePrice/SpecialPrice overrides Catalog-rule, tier and special price, resolved per customer group per-child catalogrule_product_price + catalog_product_entity_tier_price N+1 — the ~660-query cost on a 660-variant configurable
Configurable child shells in registry + LowestPriceOptionsProvider override Configurable "from" price, options, add-to-cart variant match the native used-product collection load + its per-child rule/tier N+1
Category tree pulled in one search + attribute-load plugin Mega-menu, top-nav, breadcrumbs, layered-nav category data catalog_category_entity_{varchar,int,text} UNION attribute loads → 0 category EAV reads
Indexed request_path (products) + batched category URL finder Product-card and category URLs per-item url_rewrite finder N+1 (~108 queries) → one batched query per store
OpenSearchStockRegistry + indexed stock_item Stock item / status on PDP and cart per-product cataloginventory_stock_item loads; per-line WHERE sku=? MSI preload
Indexed parent_ids + ParentIdResolver Child→parent resolution for sliders / widgets catalog_product_super_link / catalog_product_link parent N+1
Indexed swatch_options + configurable_options_<id> PDP swatch jsonConfig eav_attribute_option / _value / _swatch per-option lookups
Option dictionary carrying each option's swatch (2.8.1) Swatches\Helper\Data::getSwatchesByOptionsId for layered nav and every swatch renderer eav_attribute_option_swatch per page
Indexed category document → CategoryModelBuilder (2.8.1) The current category, breadcrumb trail, design ancestor, child list category entity load + exists-check + two EAV collection loads + url_rewrite join per category/search page
Category tree + request_path keyword on the product index (2.8.1) The URL-rewrite router's request-path lookup 2–3 url_rewrite queries on every request
Review index (2.8.0) + cached rating set PDP review list, pager total, per-review stars, review form ratings review SELECT + COUNT + one rating_option_vote per review + 3 rating queries
Cached filterable-attribute list (2.8.1) The layer's attribute list on every listing/search eav_attributecatalog_eav_attribute load per page
Category widget server (2.9.0) CMS product widgets and theme sliders full e.* collection load + attribute passes (ids from one index-only query, or from the search index)
Indexed downloadable links / samples Downloadable PDP blocks downloadable_link / downloadable_sample loads
OS-served quote-item collection (Fast Checkout) Cart / checkout line hydration the native ~217-query _assignProducts build (EAV ≈119 + MSI stock ≈71 + downloadable ≈27)
Search relevance + facets from the native fulltext index Instant search results + live layered nav the layered-navigation attribute/option SQL and category/EAV reads behind native search

Every mechanism is guarded: a doc that is missing, partial or unservable falls back to the native path automatically, and if OpenSearch is unavailable the whole storefront transparently reverts to EAV. A product missing from the index is warmed on first access — loaded natively once, pushed to the index, then served from OpenSearch thereafter.

AI-powered search relevance (search so good it's almost unfair)

We'll say it: on a real, messy catalog this is a better search experience than most billion-dollar storefronts — because it doesn't just index your words, it learns your catalog's language with AI and tunes itself. Install the extension, add a Claude API key, run the AI mapping tool once, and get near-best-possible search with almost no hand-tuning.

The AI reads your own content — attribute labels, category names and a sample of product names — and builds the language layer for you:

  • AI synonym & thesaurus discovery from your content — finds the equivalents shoppers actually type and folds them into the thesaurus (e.g. it learns front end ↔ frontend, rear end ↔ back end, u-bolt ↔ ubolt, rock racer ↔ rockracer straight from your product names). No hand-curation.
  • AI per-product keyword discovery — fills a hidden fm_search_keywords field per product with the buyer terms and aliases your copy never mentions (UTV ↔ side-by-side ↔ SxS, brand/fitment nicknames), so the right product surfaces for words that aren't on the page.
  • Grammatical & compound-word variants — spacing/hyphenation/plural pairs (t-shirt ↔ tshirt ↔ tee) discovered automatically, with a guardrail against building groups around common words.
  • Similar words & misspellings — typo tolerance plus AI-discovered misspelling/variant pairs, so a fat-fingered query still lands.
  • Smart stop words & exceptions — stop words are stripped for recall but preserved where they matter (a phrase like "side by side" survives instead of collapsing to "side").
  • Symmetric synonyms — a term and its synonyms are scored as true equals, so frontend and front end (or sxs and utv) return identical results in the same order.

Everything below is the ranking engine those AI layers feed into.

Real search wins (across niches)

Native Magento treats each of the left-hand queries as a different word and returns junk (or nothing). FastMagento learns they're the same thing — from your own catalog — and returns the right products, ranked the same either way:

A shopper types… Native Magento FastMagento (AI-learned) Niche
frontend vs front end different results / 1 hit identical results off-road / auto
top end vs top-end split, inconsistent identical — same engine parts performance auto
side by side / sxs matches the word "side" UTV / side-by-side rigs powersports
t-shirt / tshirt / tee three different result sets one result set apparel
34E / 34DD miss same cup size surfaced lingerie
ebike / e-bike / electric bike split identical cycling
usb-c / usb c / type-c split identical electronics
allen key / hex key / allen wrench miss same tool hardware / tools
a-arm / control arm miss same suspension part off-road
u-bolt / ubolt split identical trailer / auto

The magic: you don't type any of these in by hand. The AI discovers them from your products, so the wins match your niche — whatever you sell.

Relevance engine

The query builder (Model/Search/InstantSearch::buildQuery) ranks results with:

  • Exact / phrase boosting — a product whose name/keywords contain the query as a contiguous phrase outranks docs where the words are merely scattered, so "skid plate" ranks the actual skid plates above everything that just contains "plate".
  • All-terms precision boost — products matching every term (across name + keywords + description) rank above single-term hits, without ever costing recall.
  • Multi-term operatorFastMagento > Search > Multi-Term Operator: Any (OR, broadest), Most (75%), or All (AND, most precise).
  • Symmetric synonyms — a query and its synonyms are scored as true equivalents, so frontend and front end (or sxs and utv) return identical results in the same order. Phrase-level expansion means single-word ⇄ multi-word variants work (frontend ⇄ front end, a-arm ⇄ control arm), which token-level swapping cannot do.
  • Typo tolerance, in-stock boost, and a custom-ranking tie-breaker (all admin toggles).

Two synonym homes — used automatically

  • Global synonym thesaurus (Search > Synonyms) — for distinctive terms. Ships with a bundled starter database (etc/thesaurus/starter-synonyms.json, imported on install, merge-safe) covering compound/grammatical variants (front end, back end, rear end, t-shirt), colours, sizes, materials and misspellings.
  • Per-product AI keyword layer (fm_search_keywords) — a hidden, high-weight searchable attribute the AI fills per product with buyer terms and aliases (UTV ↔ side-by-side ↔ SxS, fitment/brand nicknames), so a product surfaces for terms its visible copy never mentions. Buyer phrases built from common words (e.g. "side by side") live here, where they match precisely, rather than as global synonyms that would over-broaden.

AI tools (Claude) — scrape your content, optimise search

Add a Claude API key under FastMagento > AI Assistant, then:

  • Generate Thesaurus (admin button) — scrapes your attribute labels, category names and a sample of product names, and discovers the grammatical/compound variants your copy actually uses (e.g. rear end ↔ back end ↔ backend, u-bolt ↔ ubolt, rock racer ↔ rockracer), merging them into Search > Synonyms. It is guard-railed against building groups around common words.

  • Generate Search Keywords (CLI) — populates fm_search_keywords for the catalogue, off the request path, in resumable best-effort batches:

    bin/magento fastmagento:search-keywords:generate [--from --to --batch --limit --force --dry-run]
    bin/magento indexer:reindex catalogsearch_fulltext   # make the new keywords searchable

    Enable via FastMagento > Search > AI Search Keywords. Use a fast model (e.g. Haiku/Sonnet) under AI Assistant > Model for large catalogues.

Measuring relevance

Keep a short list of golden queries for your catalogue (the searches your shoppers actually type, with the product you expect first) and re-run them after every synonym, keyword or weighting change. bin/magento fastmagento:doctor confirms the indexes, facets and instant search are wired; the ranking itself is judged against those golden queries, not guessed.

Admin: paginated attribute options (large-catalog fix)

A well-known Magento 2 shortcoming: the product-attribute Manage Options grid (and the swatch variants) renders every option on the page and re-serialises the whole set on save. Past a few thousand options the attribute-edit page slows to a crawl or crashes outright — a hard blocker for large apparel/lingerie catalogues where a single colour attribute can carry tens of thousands of combinations (solids, two-colour patterns, prints).

FastMagento replaces that screen with a paginated option manager:

  • Loads one page at a time (AJAX) — the edit page opens instantly even at 50,000 options, instead of materialising the entire array into the DOM.
  • Search across option labels, server-side (bounded query).
  • Single-row writes — adding, editing or deleting an option is one AJAX call that touches only that option's rows (eav_attribute_option + per-store value + swatch), never the whole set. A guard plugin also stops the native "save the whole array" path from ever running, so a Save Attribute click can't wipe or overwrite the option set.
  • All option types — dropdown, multiple-select, visual swatch (colour/image) and text swatch.
  • "Assigned to a product" column + filter — each row shows whether the option is used by any product (resolved efficiently from the product's backend table — int/varchar/text, so it is correct for select and multiselect). Filter the grid to Any / Yes / No to see exactly which options are dead weight (on the stress catalog's 50k color, that's 23,201 unused).
  • Bulk delete — a checkbox column with select-all-on-page and Delete Selected, plus Delete All Matching which removes the entire filtered set across all pages in one chunked action (so filter → Assigned: No → delete clears thousands of unused options at once). Both confirm with a count; if the set still includes in-use options the confirm warns before deleting.

On by default (FastMagento > … attribute_pagination/enabled); no configuration needed.

🗺️ Roadmap

Per-shopper personalisation — profiles built from what a shopper buys, views, searches for and filters by, applied to search, listings and recommendation rows, with an exploration slot for under-exposed products — now ships as the optional companion package ParkkTech FastMagento Personalisation (composer require parkktech/fastmagento-personalization, requires this package 2.7+). Core stays byte-identical without it.

Still ahead: search analytics, merchandising rules and campaign-targeted results. If a capability is described in this README it exists; if it is only listed here as ahead, it does not.

Requirements

  • Magento 2.4.x — Open Source and Adobe Commerce (content-staging schema supported since 2.10.0, see Adobe Commerce compatibility), base install.
  • OpenSearch 1.x/2.x (the engine already configured under Stores → Configuration → Catalog → Catalog Search).
  • PHP matching your Magento version.

Installation

Requirements: Magento 2.4.6+ (Open Source or Commerce), PHP 8.1+, and a configured OpenSearch (or Elasticsearch) search engine — the same engine Magento already uses for catalog search. No Hyvä/Breeze required; works on base Luma or any theme.

Composer (recommended)

composer require parkktech/fastmagento

Published on Packagist — the one-liner pulls the latest release tag. Pin a version with parkktech/fastmagento:^1.0, or track the tip of development with parkktech/fastmagento:dev-master.

Installing before it appears on Packagist, or from a fork

The package lives at github.com/parkktech/FastMagento (public). Add it as a VCS repository, then require it:

composer config repositories.parkktech-fastmagento vcs https://github.com/parkktech/FastMagento.git
composer require parkktech/fastmagento

Then, either install path:

bin/magento module:enable ParkkTech_FastMagento
bin/magento setup:upgrade
bin/magento setup:di:compile      # production mode only
bin/magento cache:flush

setup:upgrade also auto-installs large-catalog performance indexes (declarative schema, etc/db_schema.xml) — composite (attribute_id, store_id, value) indexes on the EAV value tables Magento leaves unindexed (_varchar, _decimal, _datetime). On a 500k catalog these turn an attribute-value filter from a 571,866-row scan into a 1-row seek (layered navigation, attribute filters, admin grids). No manual SQL, and they're removed cleanly if you uninstall.

Manual (no Composer)

mkdir -p app/code/ParkkTech/FastMagento
cp -R <module-source>/* app/code/ParkkTech/FastMagento/
bin/magento module:enable ParkkTech_FastMagento
bin/magento setup:upgrade
bin/magento cache:flush

Then build the indexes (all four):

bin/magento indexer:reindex fastmagento_product fastmagento_category fastmagento_attribute_option fastmagento_review

Upgrading to 2.8+ — reindex once after setup:upgrade: fastmagento_product (the product index gains the request_path keyword field URL routing needs; the doctor reports until it is there), fastmagento_attribute_option (options now carry their swatch), and the new fastmagento_review. Put fastmagento_review in schedule mode like the other three.

After any composer change on a production box, reload PHP-FPM (or call opcache_reset()) once setup:di:compile / setup:upgrade / cache:flush are done: workers otherwise keep the old composer autoload and return 500s until opcache revalidates — and Varnish then marks the backend sick for its probe window.

Uninstallbin/magento module:uninstall --remove-data ParkkTech_FastMagento runs the module's Setup/Uninstall (2.8.1): it deletes the four OpenSearch serving indices, the mview changelog tables, indexer/mview state, cron schedule rows, flags and the module's settings. Uninstall companion modules (personalisation, checkout) first. Magento then tries to composer remove with its own embedded composer, which has no auth.json; on a project with private or VCS repositories that step fails with "Could not authenticate against github.com" — finish with composer remove parkktech/fastmagento by hand. Details in INSTALL.md.

Configuration reference

All settings live under Stores → Configuration → FastMagento. Every value is store-view scoped unless noted; sensible defaults ship in etc/config.xml so the extension is usable immediately after install.

Indexing

Setting Default What it does
Enable Real-time Indexing On Update OpenSearch docs synchronously on catalog save (mview).
Enable Cron Indexing On Let the mview cron flush pending changes on a schedule.
Product Index Prefix products Index name prefix for the product serving index.
Category Index Prefix categories Index name prefix for the category serving index.
Review Index Suffix reviews Index name suffix for the review index (2.8.0).

Serving

Every switch below is on by default and falls back to the native path when the index cannot answer.

Setting Default What it does
Serve Stock Status / Category Tree / Category Attributes From The Index On The original serving switches (stock badge, category children, category EAV attributes).
Serve Product Reviews From The Index On PDP review list, pager total and per-review stars from the review index (2.8.0).
Render The Category Menu Inline Under Varnish (+ block names) On No ESI round-trip for the menu on a Varnish miss (2.8.0).
Cache Hyvä Section Data Per Store On Block-caches default-section-data per store and currency (2.8.0).
Cache Layered Navigation HTML (+ lifetime, block names) On, 3600 s Per category/query, filters, store, currency, group (2.8.0).
Serve The Current Category From The Index On Category controller, breadcrumbs, design walk, child list from the document (2.8.1).
Resolve Product And Category URLs From The Index On The router's request-path lookup; redirects and custom rewrites stay in url_rewrite (2.8.1).
Cache The Layer's Filterable Attribute List On Per store and list class, cleaned on attribute save (2.8.1).
Serve Swatches From The Index On Swatch type/value per option from the option dictionary (2.8.1).
Serve Category Widgets And Sliders From The Index On CMS widgets and theme sliders of the "products of category N" shape (2.9.0).
Widget Product Ids Come From Database Database = byte-identical order from one index-only id query; Search index = no MySQL, ties by product id (2.9.0).

Instant Search & Relevance

Setting Default What it does
Searchable Attributes & Weights name, sku, descriptions The fields searched and their relative weights (higher = ranks stronger). One grid instead of per-attribute Search Weight.
Typo Tolerance On Fuzzy matching (fuzziness: AUTO) so misspellings still match.
Boost In-Stock Products On Rank in-stock above out-of-stock as a tie-breaker.
Custom Ranking Attribute / Direction Optional numeric secondary sort after text relevance (e.g. bestseller, rating).
Multi-Term Operator Any (OR) How multiple words combine: Any (broadest), Most (75%), All (AND, most precise).
Exact / Phrase Match Boost 4 How hard a contiguous-phrase match outranks scattered-word hits. 0 disables.
Synonyms / Thesaurus starter DB Equivalence groups (one per line). Ships with a bundled starter database; extend by hand or with the AI tool.
Stop Words common English Words ignored in queries.
Facet Attributes Comma-separated attribute codes that build the search-results facets (single-select).
AI Search Keywords Off Search the per-product AI keyword layer (fm_search_keywords). Enable after populating it (below).
AI Keyword Weight 8 Search weight of the AI keyword field when enabled.
AI Keyword Source Attributes facet attrs Attribute codes whose labels give the AI product context when generating keywords.

AI Assistant (Claude)

Setting Default What it does
Claude API Key Stored encrypted. Enables the AI thesaurus + keyword tools. Leave blank to disable AI.
Model claude-opus-4-8 Claude model id. Use a fast model (Haiku/Sonnet) for large keyword runs.
Max Catalogue Terms 1200 Upper bound on vocabulary sent to the model (keeps prompts bounded on big catalogs).
Generate Thesaurus button Scrapes your attribute labels, categories and product names → discovers synonym/compound groups → merges into Synonyms.

Fast Checkout

Setting Default What it does
Enable Fast Checkout On Master toggle: serve cart/checkout line products from the index + optimistic stock. Cannot oversell (SKU-gated at placement); falls back to native for anything not fully indexed.
OS-Serve Quote Items (advanced) Off Serve the quote-item collection from OpenSearch by itself.
Optimistic Stock (advanced) Off Skip the redundant per-load MSI preload; rely on the placement-time gate.
Fast Stock Sync (advanced) Off Patch only stock fields of affected docs instead of a full reprojection.
Configurable Line Name parent Show the configurable (parent) or purchased child name on cart/checkout lines.

Architecture

How it works end-to-end

MySQL stays the source of truth. Two background paths keep an OpenSearch document current for every product and category; the storefront read path then answers from those documents instead of EAV, with an automatic native fallback.

flowchart LR
    subgraph WRITE["Write path — keeps the index live"]
        direction TB
        C["Catalog / price / stock /<br/>category change"] --> IDX["Indexers via mview:<br/>fastmagento_product<br/>fastmagento_category"]
        ORD["Order · refund ·<br/>MSI stock write"] --> SS["StockSyncer<br/>after fastcgi_finish_request"]
        RULE["Catalog-rule reindex"] --> CRS["CatalogRuleSyncer"]
    end

    IDX --> OS[("OpenSearch<br/>magento2_products<br/>magento2_categories")]
    SS --> OS
    CRS --> OS

    subgraph READ["Read path — serves the storefront"]
        direction TB
        REQ["Storefront request<br/>PDP · cart · search · menu"] --> INT{"Interception<br/>layer"}
        SHELL["ShellNoEavProduct<br/>real Product · no EAV load"] --> RESP["Rendered page / JSON"]
        EAV[("MySQL EAV<br/>native fallback")] -.->|"warm-on-miss"| OS
        EAV --> RESP
    end

    INT -->|"hit"| OS
    OS -->|"_source doc"| SHELL
    INT -.->|"miss / OS down"| EAV
Loading

Write path. Catalog/price/stock/category edits reproject through the fastmagento_product / fastmagento_category mview indexers. Stock changes from orders, refunds and MSI inventory writes are reprojected by StockSyncer after the response is flushed. Catalog-rule recalculations patch per-group rule prices into the docs via CatalogRuleSyncer — no manual reindex needed.

Read path. Every storefront read is intercepted and answered from an OpenSearch document by a ShellNoEavProduct (a real Magento\Catalog\Model\Product subclass whose load() is a no-op and whose getters read the indexed _source). Because it is a genuine Product/Category, third-party blocks, plugins and SEO modules keep working unchanged. A missing/partial doc, or an OpenSearch outage, transparently falls back to native EAV — and a product missing from the index is warmed on first access (loaded natively once, projected, then served from OpenSearch thereafter).

Two serving indexes

Indexer id Index Each document carries
fastmagento_product magento2_products name, sku, type, visibility, status; regular / special / tier price + a per-customer-group catalog-rule price map; is_in_stock + qty + full stock_item; media gallery; category_ids / category_names; canonical request_path + url_key/url_path; every custom attribute with option ids pre-resolved to labels; configurable swatch_options + configurable_options_<id>; batched composite child_products (each with its own price/stock/rule map); downloadable links/samples; parent_ids
fastmagento_category magento2_categories tree structure (parent_id, path, level, position, children_count); menu flags (include_in_menu, is_active, is_anchor, display_mode); url_key / url_path / request_path

Both track incremental changes through mview and stream documents to OpenSearch in NDJSON bulk chunks of 200 for flat memory use at catalog scale.

Read path — the interception points

The read path is intercepted at these choke points, so nothing on the storefront issues the native EAV load:

  • Magento\Catalog\Model\Product::load — the single product-load interception (PDP, cart).
  • ProductRepositoryInterface::getById — repository / REST / GraphQL reads.
  • Product\CollectionFactory and the link (related / up-sell / cross-sell) collection.
  • Category\Collection::_loadAttributes and UrlFinderInterface — category data + URLs.
  • TierPrice / CatalogRulePrice / SpecialPrice price models, LowestPriceOptionsProvider, the configurable type model, and StockRegistryInterface.
  • Quote\Item\Collection::_assignProducts — the Fast Checkout quote-item serving (global, so it covers webapi_rest / graphql checkout; admin and cron always stay native).

Real-time stock sync (detail)

MSI mutates stock through SKU-keyed tables (inventory_source_item / reservations) that a product-entity mview cannot map, so stock is additionally kept live by:

  • sales_order_place_after observer — reservation / decrement on order.
  • sales_order_creditmemo_save_after observer — back-to-stock on return/refund.
  • catalog_product_delete_after observer — drops the deleted product's document.
  • SourceItemsSaveInterface / SourceItemsDeleteInterface plugins — admin grid, imports, ERP integrations and bin/magento inventory:*.

Each reprojects only the affected products (and their configurable/grouped/bundle parents), skips no-op saves (compares live stock against the indexed values), and runs after fastcgi_finish_request — best-effort and logged, so a sync failure can never break checkout, a refund or a stock save. The stock write remains native Magento (StockManagement); the sync only keeps the index current, and order placement re-checks salable quantity by SKU, so it can never oversell.

Operations & maintenance

Reindexing

bin/magento indexer:reindex fastmagento_product      # product serving index
bin/magento indexer:reindex fastmagento_category     # category serving index
bin/magento indexer:reindex catalogsearch_fulltext   # native search index (after AI keyword runs)

Day to day, real-time + cron indexing keep both indexes current; a full reindex is only needed after a bulk import or a mapping change. Reindexing streams documents in bulk chunks, so memory stays flat regardless of catalog size.

AI search-mapping tools

The intended setup flow — install → add a Claude key → run the mapping tools → best search:

  1. Thesaurus (admin): FastMagento > AI Assistant > Generate Thesaurus. Scrapes your own content (attribute labels, categories, product names) and merges discovered synonym/compound groups into Search > Synonyms. Re-runnable; merge-safe.

  2. Per-product keywords (CLI): populate fm_search_keywords, then reindex fulltext.

    # dry-run a sample first (no API calls, no writes)
    bin/magento fastmagento:search-keywords:generate --dry-run --limit=25
    
    # generate for the whole catalogue (resumable; re-run continues where it left off)
    bin/magento fastmagento:search-keywords:generate --batch=25
    
    # scope / control
    #   --from N --to N     entity_id range
    #   --limit N           cap this run
    #   --force             regenerate products that already have keywords
    bin/magento indexer:reindex catalogsearch_fulltext

    Then enable Search > AI Search Keywords. For a 14k+ catalog, set a fast model under AI Assistant > Model (Haiku/Sonnet) — keyword extraction does not need the largest model.

Verifying it works

Confirm pages are served from OpenSearch with minimal SQL by counting the queries one render costs: enable MySQL's general log (SET GLOBAL general_log = 'ON'), request the page once warm, and read the log back. A product page should show no product/EAV/catalog queries — every product read comes from the index. bin/magento fastmagento:doctor reports the serving wiring the same way, per surface.

OpenSearch quick reference

# Count product docs
curl -s "http://localhost:9200/magento2_products/_count?pretty"

# Count category docs
curl -s "http://localhost:9200/magento2_categories/_count?pretty"

# Fetch a single doc by id
curl -s "http://localhost:9200/magento2_products/_doc/<entity_id>?pretty"

# View the mapping
curl -s "http://localhost:9200/magento2_products/_mapping?pretty"

(Index names use the store's opensearch_index_prefix; the defaults are shown.)

Supported product types

Type Read (PDP/PLP/search) Add to cart / order
Simple / Virtual ✅ served from OpenSearch
Downloadable ✅ links + samples served
Configurable ✅ swatches + jsonConfig served ✅ (option→child matched from OpenSearch)
Grouped / Bundle ✅ indexed ✅ served — additional confirmation needed

Adobe Commerce (EE) compatibility

Adobe Commerce's content staging rewrites the catalogue schema: catalog_product_entity and catalog_category_entity gain a row_id key (one row per scheduled version), and every child table that referenced entity_id references row_id instead — the EAV value tables, tier prices, media gallery values, and the parent side of catalog_product_super_link, catalog_product_relation, catalog_product_link, bundle options/selections, custom options and super attributes. Since 2.10.0 every raw query in FastMagento that touches one of those tables goes through Model\Db\EntityLink, which reads the link field from Magento's MetadataPool and, on Commerce only, joins the entity table to translate row_identity_id. On Open Source the helper adds nothing: no join, the same SQL, the same query counts.

What that means in practice:

  • Documents are keyed by entity_id on both editions, so an index built on Commerce looks exactly like one built on Open Source (verified document-for-document, see below). The currently active version of a product is what gets indexed; Commerce's own reindex triggers pick up a scheduled version when it applies. Admin preview of a future date shows current data on OpenSearch-served pages.
  • bin/magento fastmagento:doctor has a Commerce section: which schema edition the install runs on, and warnings when category permissions or B2B shared catalogs are active — OpenSearch-served listings and search do not apply those per-customer-group visibility rules yet, so keep the serving flags off on the store views that depend on them.
  • Textarea / WYSIWYG / Page Builder attributes are mapped as analysed text under attributes.* (option attributes stay keyword, with ignore_above), so a 100 KB size chart can no longer reject a document with "immense term in field=attributes.".

How it was verified without a Commerce licence: an Open Source install is converted to the Commerce shape (row_id, created_in, updated_in, child tables re-keyed, MetadataPool link field row_id) with the scripts under docs/tools/commerce-sim/ of the demo repo, Magento's own indexers are run on it, and every FastMagento index is diffed against the same catalogue indexed on the untouched Open Source install: 0 differing documents across products, categories, attribute options, reviews and the native search index (the only extra fields on the Commerce side are the version columns Magento itself exposes on the product). A real Adobe Commerce install additionally applies its version filter to every select on a staged table, which is why the helper never filters on created_in/updated_in itself.

Known limitations / roadmap

  • Adobe Commerce category permissions and B2B shared catalogs are not applied by OpenSearch-served listings and search (the doctor warns when they are active). Permission-aware serving is the next Commerce milestone.

  • The category page's product grid still renders natively (MySQL); the fully OS-served, live product listing today is the search results page. OS-serving the category/PLP grid is the next serving-layer milestone.

  • Grouped and bundle add-to-cart is served; broader edge-case confirmation is still pending.

  • The serving index projects the default store view; per-store serving is tracked separately for multi-store setups.

  • Storefront layered-nav & PDP option labels (select and multiselect) are now served from the OpenSearch option dictionary (fastmagento_attribute_option), so Shop By / Additional Information resolve labels with 0 MySQL. The instant search-results-page facets still read the native fulltext index for buckets, where a doc's option-id and option-label arrays sort independently — extending those facets to read the same dictionary is the remaining step to full multi-select facet labels on the live SERP.

  • Fast Checkout is on by default but changes checkout stock behaviour (optimistic stock relies on the placement-time SKU gate), so validate it with a real order — guest + a logged-in group, plus a deliberately over-qty line — after go-live; it also assumes a fresh price/rule projection (the always-ready rule sync keeps it current after the first reindex). Turn the master toggle off to fall back to a fully native cart.

Credits

FastMagento is built and maintained by ParkkTech.

Need Magento performance work or a custom extension? Talk to ParkkTech »