Appearance
Differences from Standard Magento
Lynx runs inside Magento: the same controllers, models, observers, plugins, APIs and admin. What changes is how a storefront page is built and sent to the browser, plus a few runtime shortcuts that come with it. This page lists what behaves differently on a Lynx store, so you know what your extension can rely on.
When a request counts as Lynx
A request is a Lynx request when the design theme of the store view is a Lynx theme, or a child theme of one (Content › Design › Configuration). Everything on this page applies only then. Store views on another theme, the admin, cron, CLI, REST and GraphQL behave like standard Magento, except for the compiled configuration, which covers every area.
Two settings narrow it down:
- Page Fallback (Stores › Configuration › Lynx › Theme Settings › Page Fallback) renders the full action names you list, such as
shipping_tracking_popupormultishipping_*, with a standard Magento theme. Those pages are not Lynx pages and get none of the differences below. When the Lynx storefront navigates to one of them, it switches to a full page load. - Render with Lynx on a CMS page or category does not change the theme. It chooses what fills the content area of that page: its own PageBuilder layout when on, the standard content when off. See Content.
Page rendering
A Lynx page is a single-page app. The first visit returns HTML with the page data embedded as JSON. Following a link afterwards fetches only that JSON (an Inertia request, sent with the X-Inertia header) and the client swaps the page without a reload. The page itself is a PageBuilder layout: header, footer and a content area chosen by the controller's full action name.
| Magento feature | On Lynx stores | What to do instead |
|---|---|---|
RequireJS, KnockoutJS, UI components, jQuery and jQuery UI widgets, x-magento-init, data-mage-init | Not loaded on the storefront | Build a Lynx component |
Layout XML of modules and themes (default.xml, catalog_product_view.xml…) | Not collected. Only files named lynx_*.xml are read, and every handle also gets a lynx_ twin (catalog_product_view → lynx_catalog_product_view) | Put server-side layout needs (head metadata, block arguments) in lynx_<handle>.xml; put visible content in a component |
Blocks and .phtml templates | Render as usual when declared in a lynx_*.xml file, inside the few containers Lynx's page layout has (head.additional, lynx.after.body.start, lynx.main.container, lynx.before.body.end). Blocks declared only in regular layout files are never created, so plugins on them and their view models do not run. Lynx itself declares very few blocks | Declare the block in lynx_<handle>.xml; for content merchants place or that changes as shoppers navigate, a component with a PHP provider. See Layout XML |
Page layouts (1column, 2columns-left…) and the Layout field of pages, categories and products | Ignored: every page uses Lynx's own page layout | Arrange the page in PageBuilder |
<head> assets added by layout XML (<css>, <script>, <link>) | Only those declared in lynx_*.xml files | Load scripts from a component when it mounts |
| Head output on later navigations | Only the full page load renders <head>. Inertia requests update the document title and the <body> class; other head tags stay as they were | Keep per-page data in page props, not in <head> |
| Magento's LESS stylesheets | Not used; Lynx ships Tailwind CSS styles built from the design settings | Style components with the theme's tokens |
Pages of other modules (/blog/post/view, /brand/index/index…) | The controller runs, but only the header, footer and the PageBuilder layout saved for that action name show; blocks from its regular layout files do not (those from a lynx_ file of the module do) | Return an Inertia page from the controller, or list the route in Page Fallback |
What still runs on every Lynx request: routing and controllers, controller_action_* and other events and observers, plugins on models and services, page config set by controllers and lynx_*.xml files (title, meta tags, <html> and <body> attributes), the full page cache and its cache tags, sessions, form keys and flash messages. Lynx keeps Magento's title, meta and canonical for catalog pages by declaring the core blocks that set them in its own lynx_*.xml files, rendering nothing. If your extension sets head data from a block's _prepareLayout(), declare that block the same way.
CMS content and widgets
| Magento feature | On Lynx stores | What to do instead |
|---|---|---|
| Content › Widgets instances placed on pages or containers (widget layout updates) | Not shown. The stored layout updates are skipped when the layout is merged | Add the content with a PageBuilder component |
| CMS page Content field | Shown in the content area when Render with Lynx is off, or when the page has no Lynx layout. Directives (, , , ) are expanded on the server and the HTML is inserted as is | Scripts inside that HTML do not run; use a component for anything interactive |
| CMS page Layout Update XML and custom layout update files | Not applied, as with other layout XML | Design the page in PageBuilder |
| Category Display Mode and Add CMS Block | Not rendered; the category page shows its PageBuilder layout | Turn on Render with Lynx for that category and design its content |
| Product and category descriptions | Rendered through Magento's output helper, so directives work; the HTML is inserted without running scripts | Same as CMS content |
/ placeholders inside layout XML | Left as literal text | Build URLs in PHP |
Customer data sections
Private content still comes from customer/section/load and the sections declared in sections.xml. Your SectionSourceInterface classes keep working. Differences on the client:
- Storage. Lynx keeps sections in IndexedDB (database
lynx_customer_data), not in themage-cache-storagelocalStorage keys. Scripts that read localStorage directly find nothing. - Invalidation. There is no global AJAX listener. A Lynx component that posts to an action gets that action's section list from the server (resolved from
sections.xml, exact route keys only, no wildcard matching) and reloads those sections itself through the customer data store. Your component does the same after its own requests. - Wishlist section. On Lynx stores the
wishlistsection returns the counter and count but an emptyitemslist, since no block is rendered for it.
Translations
| Magento feature | On Lynx stores | What to do instead |
|---|---|---|
Database translations (the translation table) | Not applied | Use i18n/<locale>.csv in a module or theme, or a language pack |
| Inline Translation (Developer › Translate Inline) | Has no effect, because it writes to the database; leave it off for Lynx store views, or text shows its translation markers | Same as above |
JavaScript translation dictionary (js-translation.json, $t(), mage/translate) | Not loaded; components receive text already translated on the server with __() | Translate labels in your provider or controller, see Translations: on the server only |
| Text entered in PageBuilder | Translatable per store view in the editor (text, textarea and rich text settings), then passed through __() | Translate in the editor, or add the default string to a CSV |
When lynx:compile has been run, the translation dictionary of each Lynx theme and locale is read from the compiled files, so a CSV change shows only after bin/magento lynx:compile translate.
Compiled configuration
OOCS_LynxCompiler merges Magento's XML configuration once at deployment instead of after every cache flush:
bash
bin/magento lynx:compile # every compiler
bin/magento lynx:compile events routes # only some of themIt covers most of the XML Magento merges at runtime, such as events.xml, routes.xml, config.xml, view.xml, sections.xml, webapi.xml, extension_attributes.xml, csp_whitelist.xml, ACL, menu, indexer and message queue files, plus the Lynx translation dictionaries. di.xml is not included: setup:di:compile already handles it. The result lives in generated/lynx_compile and is read in every area and on every store, not only Lynx store views.
The compiled files are a snapshot:
setup:upgrade,module:enableandmodule:disabledelete them together withgenerated/code, so runlynx:compileagain afterwards.- Changing any covered XML file by hand, including in a third-party module, needs a new
lynx:compile. Until then the store keeps using the old configuration without any warning.cache:flushdoes not clear the compiled files. - Without compiled files, Magento merges configuration as usual, only slower.
- To remove them, use System › Cache Management › Flush Lynx Compile, or delete
generated/lynx_compile.
Recommended deployment order: setup:upgrade → setup:di:compile → lynx:compile → static content deploy.
Checkout
On a Lynx store, checkout/index/index shows the Lynx checkout instead of Magento's KnockoutJS checkout. The controller and quote APIs are Magento's own; the steps, address form, shipping methods and payment methods are Lynx components.
- Offline payment methods (check / money order, bank transfer, cash on delivery…) show without extra work. Online (gateway) methods are hidden until they have a Lynx renderer. See Payment Methods.
- A shipping method that needs extra input, such as a pickup location, needs a Lynx renderer. See Shipping Methods.
- Checkout
layoutProcessorplugins,checkout_index_index.xmlchanges andcheckoutConfigJavaScript mixins do not apply. - The checkout always opens as a full page load, never as an in-app navigation.
- Checkout with multiple addresses is in the default Page Fallback list, so it renders with a standard theme once Page Fallback is on.
Other runtime changes
These skip work the Lynx storefront does not need. Most have no visible effect, but an extension can notice them:
- Layout cache per theme. The merged layout is cached per Lynx theme rather than per theme and store view.
- CSP headers. Inertia (JSON) responses carry no Content Security Policy header; the full page load does.
- RTL. Store views with an Arabic, Persian or Hebrew locale get
dir="rtl"on<html>. - UI component factory. Created lazily on the storefront, REST and GraphQL, since Lynx pages declare no UI components.
- Persistent cart check. The per-request persistent quote check returns early when there is no quote or the customer is signed in, and calls Magento's own observer only for a persistent quote.
Making your extension work on Lynx
| Your extension has | On Lynx |
|---|---|
| Observers, plugins, models, cron, admin pages, REST and GraphQL endpoints | Work unchanged |
Storefront UI in layout XML, .phtml, KnockoutJS or jQuery widgets | Rebuild it as a Lynx component with a PHP provider, so merchants place it in PageBuilder |
| A storefront page (controller with its own layout) | Return an Inertia page from the controller, or list its route in Page Fallback |
| Forms posting to your controllers | Submit with an Inertia form or fetch, then reload the customer data sections the action invalidates (see Forms) |
| Tracking or analytics scripts | Listen to the analytics bus instead of DOM events or KnockoutJS observables (see Forms) |
| Translations in the database or through Inline Translation | Move them to i18n/<locale>.csv or a language pack |
| Widget instances or CMS blocks placed through layout updates | Offer a PageBuilder component instead |
| Payment or shipping method with its own checkout UI | Add a payment renderer or shipping renderer |
| New or changed XML configuration | Run bin/magento lynx:compile after installing or updating |
For a complete walkthrough with a worked example, see Making a Luma Extension Compatible.