The largest EverShop release since 2.0. It consolidates the previously drafted-but-unpublished 2.1.3 work (React 19) with four months of development on top of v2.1.2: a visual page builder, a blog module, entity custom fields (metafields), a multi-language storefront with a translated admin, a rebuilt shipping and fulfillment stack, built-in cloud file storage, product recommendations, and a substantial security and performance pass.
31 database migrations across 10 modules run automatically on first start. Please read Breaking Changes and Upgrade Notes before upgrading. This release also contains fixes for several security vulnerabilities — upgrading promptly is recommended.
Highlights
- Visual Page Builder — drag-and-drop storefront editing with drafts, publish, and scheduled rollouts.
- Blog — a full blog module: posts, categories, tags, comments, storefront pages and widgets.
- Metafields — typed custom fields on products, categories, collections, customers, orders, and the shop itself, with theme provisioning.
- Multi-language — runtime-translated storefront with locale-prefixed URLs, plus an independently-translated admin (17 locales bundled).
- Shipping providers & multi-shipment — pluggable shipping providers, first-class shipments with per-shipment status, carrier/label integration, package management.
- Cloud file storage — S3, Azure Blob, and Google Cloud Storage built in, configured from the admin.
- Product recommendations — related products, frequently-bought-together, upsell and cart cross-sell shelves.
- React 19, per-IP rate limiting, sitemap.xml + robots.txt, landing pages, automatic URL redirects, and large-catalog performance fixes.
Breaking Changes
- Upgraded to React 19 (from React 17) (#863). The framework runs on React 19, and because EverShop resolves React through a single hoisted copy (webpack alias), every extension and theme now runs on React 19 as well. Update React-17-era code:
defaultPropson function components is removed — use ES default parameters (function C({ x = 1 })).- String refs,
findDOMNode, legacyReactDOM.render/ReactDOM.hydrate, and legacy context are removed — use callback/useRefrefs,createRoot/hydrateRoot, andcreateContext. - Do not call the translation helper
_()at module scope (top-levelconstarrays/objects): it freezes the translation at import time and causes an SSR/client hydration mismatch under React 19. Call_()inside the component (render time). - Read client-only state (e.g.
window.location) in auseEffect, not in auseStateinitializer, so the first client render matches the server. - See
specifications/react-19-upgrade.mdfor the full audit and migration detail.
react-toastifyremoved — toasts now usesonner. Extensions importingreact-toastifymust switch:import { toast } from 'sonner'(thetoast.*call surface is close to drop-in) and replace<ToastContainer/>with sonner's<Toaster/>.- Shipping is now provider-based (#929). The old flat zone → method → rate model was replaced by shipping providers. Existing zones, methods, and rates are migrated automatically into the built-in Core provider (
core_shipping_method,core_shipping_method_rate,shipping_zone_providertables); the legacy tables/columns are dropped at the end of the migration chain. Custom code that queried the legacy shipping tables, or extensions that added shipping methods directly, must move to the provider registry (which also supports live-quoted rates with per-provider timeouts). - Fulfillment is now multi-shipment.
order ↔ shipmentwent from 1:1 to one-to-many: each shipment carries its own item assignments and its own status, andorder.shipment_statusis an item-based rollup over physical items. The pre-shipped state was dropped — legacypending/processingshipment rows are collapsed toshippedand order rollups are backfilled by migration. Consumers of the shipment REST/GraphQL shapes must adapt. - Widget storage refactor. The
widgettable was renamed towidget_instance, and placement moved to awidget_placementtable with one row per (widget instance, route, area). Widget instances are now scoped to the theme they were created under. Any direct SQL or custom tooling against the oldwidgettable breaks; the admin UI, REST, and GraphQL surfaces are updated. - Branding and several config keys moved from
config.jsonto admin settings. The logo, favicon, social banner, and Google Analytics 4 ID are now managed under Store Setting → Branding; currency, display timezone, weight and dimension units under Store Setting; catalog, pricing, and tax options under their own settings pages; guest checkout under checkout settings. Legacy config values are still honored as fallbacks — exceptthemeConfig.logo, which was removed entirely: re-upload your logo in the admin after upgrading. category.include_in_navdropped. Storefront navigation is built with the menu widgets now (including the new footer menu widget), not by flagging categories. Rebuild your navigation with the menu widget if you relied on this column; GraphQL no longer exposesincludeInNav.url_rewrite.languagedropped. URLs are locale-agnostic: locale is expressed as a URL prefix (/de/<slug>), and the slug is shared across locales.node-cronupgraded 3 → 4. Verify customregisterJobschedule expressions against node-cron v4 semantics.--skip-minifyremoved (and thebuild-fastnpm script). Builds are always minified; overall build time was optimized instead (see Performance).- Removed the unused CKEditor packages (
@ckeditor/ckeditor5-build-classic,@ckeditor/ckeditor5-react).
New Features
Visual Page Builder (new pageBuilder core module)
A drag-and-drop visual editor for the storefront at /admin/page-builder:
- Edit any storefront route (plus CMS pages and landing pages) by composing widgets into the theme's areas, with layout-aware drag/drop and moves.
- Draft → publish workflow: changes accumulate in a per-admin, per-theme draft changeset with per-widget auto-save; publish immediately or schedule a rollout — rollout plans remain editable and cancelable until they run.
- Inline editing on the canvas: text and images can be edited in place; the image picker supports cloud storage.
- Layers panel, a "Globals" view for site-wide areas, a session/page picker (Homepage first), per-widget styling controls, and link fields that resolve products/categories/CMS pages/blog posts through a unified link resolver.
- Widget admin redesign to match: theme-scoped widget instances and a per-(route, area) placements model.
- Hardened in a dedicated security pass and covered by an e2e test suite.
Blog (new blog core module) (#285)
- Posts, categories, and tags with per-post SEO descriptions; comments and reactions.
- Storefront blog pages plus blog widgets for the page builder; Blog Home is available in the link picker with proper thumbnails.
Entity custom fields — metafields
- Typed metafield definitions (text, long text, rich text via the block editor, number, boolean, date, select, JSON/group, and list variants) attachable to products, categories, collections, customers, orders, and the shop.
- Values stored per entity (JSONB
meta_data), validated with AJV on write; admin editing cards on the relevant edit pages; audience-gated GraphQL exposure (customer-visible vs admin-only). - Theme integration: a theme can declare metafield definitions in
theme.json— they are provisioned at theme activation and on boot (with attribution and conflict reporting) — and render values with the new storefront<Metafield>component. - The footer copyright line is now a shop metafield, editable from the page builder.
Multi-language storefront & translated admin (#311)
- Runtime translation — no rebuild needed. Per-locale CSV dictionaries live in the project's
translations/folder; English is the source language. - Enabled storefront languages and the default language are admin settings. Non-default locales get URL prefixes (
/de/...) with canonical handling; REST APIs take anX-Localeheader. - The admin panel has its own language, independent of the storefront.
- Translations are bundled for 17 locales (de, el, es, fa, fr, hu, it, mn, nb, ne, nl, pt, ru, sr, ta, vi, zh).
Landing pages, root-level CMS URLs & automatic URL redirects
- Landing pages: standalone marketing pages whose body is built entirely in the page builder, served at root-level friendly URLs, with an admin grid, duplication, status/scheduling, and SEO fields. Landing pages win URL collisions deterministically.
- CMS pages moved to root-level URLs (
/<url_key>); old/page/<url_key>URLs permanently redirect (301). Existing pages are backfilled by migration. - Automatic URL redirects: renaming a product/category/CMS/landing-page slug now captures a redirect from the old path (302 via a new
url_redirecttable), with redirect-chain collapse and periodic cleanup — old links and bookmarks keep working.
Sitemap & robots.txt (#506)
- Auto-generated
/sitemap.xml(a sitemap index plus per-content-type children) covering products, categories, CMS pages, and landing pages, with a collector registry so extensions can add their own URL sources. - Multi-language stores get one entry per enabled locale with self-referential
hreflang+x-defaultalternates. - Regenerated by a cron every 30 minutes with cheap change-detection (skips writing when nothing changed); served statically.
- Dynamic
robots.txtwith an absoluteSitemap:line — overridable via a setting or by shipping a physicalpublic/robots.txt.
Shipping providers, multi-shipment & carrier integration (#929)
- Provider abstraction: shipping rates come from registered providers; the built-in Core provider reproduces the classic zone/method/rate setup (flat, percentage, price/weight-based rates, API-calculated rates). Extension providers can quote live rates with configurable timeouts.
- Multi-shipment: ship an order in several packages — each shipment has its own items, status, tracking, and (optionally) purchased label (
label_url/format persisted). Order-level shipment status rolls up from items; digital items are excluded. - Carrier registry: register carriers that create labels, generate tracking URLs, and push status updates back into EverShop; per-method default carrier and service code pre-select in the ship dialog; carrier tracking URLs from aggregators (Shippo/EasyPost/ShipStation-style) are persisted.
- Package management: admin-managed parcel sizes, product → package assignment, and dimension/weight snapshots that flow
cart_item→order_item→ carrier requests.
Cloud file storage: S3, Azure Blob & Google Cloud Storage
- The three providers are now built into core (previously separate extensions), selected and configured at runtime from a new System Setting page — no restart needed; credentials can also come from env/config, and secrets are masked in the admin.
- Fixes doc-verified defects of the old extensions along the way: regional S3 endpoints (plus custom endpoint / path-style for R2 and MinIO), correct Content-Type on upload, listing beyond 1,000 keys, Azure public-access handling, and URL encoding.
- The storage host is automatically allowed for the
/imagesoptimizer; deletes are idempotent across providers.
Product recommendations
- Related Products: rule-based (same category / same collection / same attribute values, with price band, priority ordering, and manual picks), configurable globally, per category, and per product.
- Frequently Bought Together: powered by co-purchase statistics (association confidence + lift with configurable thresholds), rebuilt nightly and on demand from order history. Also reaches the cart page: a widget aggregates co-purchase candidates across all cart items (per-pair gating, strongest-affinity ranking, in-cart items and their variants excluded), exposed as
Cart.crossSellProducts. - Upsell shelf: derived automatically from the related-products rules restricted to pricier products, exposed as
Product.upsellProducts— nothing extra to configure. - Three new page-builder widgets render the shelves on product pages with full status/stock/visibility gating and variant-group awareness; admin cards on product and category editing (modes, manual picks, computed-candidate previews); new REST endpoints for manual links and stats recompute; new GraphQL fields
Product.relatedProducts/Product.crossSellProducts.
Checkout
- Zero-total orders (100% discounts, free products) check out through an automatic "No payment required" method instead of erroring (#996).
- Guest checkout toggle — an admin setting; when off, checkout requires login (with redirect back). Defaults to allowed; the legacy
checkout.allowGuestCheckoutconfig is honored as fallback. - Mobile checkout: the order summary now comes first as a sticky, collapsible bar; shipping/payment/billing option cards are clickable across their whole surface.
- Fixed: shipping note not showing on checkout (#955); billing country missing/restricted for virtual products (#934).
Storefront theme refresh & UX
- The default frontStore theme was re-skinned, with aligned category-page filters and product counts, a constrained footer, and grid-safe no-image placeholders.
- Accessibility pass: product-card Add-to-Cart is reachable by keyboard and touch (not hover-only), field error messages are wired to inputs via
aria-describedby. - Unavailable variant options are disabled in the storefront selector (#964); storefront images are sized from the configured aspect ratio (less layout shift); slideshow slides size to their images with real mobile dimensions.
- Live SEO snippet preview on product and category editors.
- Product duplication (#276): duplicate from the product grid into a prefilled creation form (suffixed sku/url_key/name, collections copied,
product_duplicatedevent). - New footer menu widget; refined collection/blog/mosaic widget layout settings.
Platform & operations
- Per-IP rate limiting built in (#1001): pages ~300 req/min, APIs 120 req/min, and login/registration/password-reset 8 attempts per 15 minutes; static assets exempt. Honors
TRUST_PROXY_HOPSfor correct client IPs behind proxies (default: 1 hop). EVERSHOP_HOME_URLenvironment override for the public base URL, validated at boot (#960).- Theme content tooling: new
theme:status,theme:uninstall, andtheme:export-contentcommands; themes can ship installable content (theme.json) and serve their ownpublic/assets (#830, #845 — thanks @starry-osean).
Security
Upgrading is recommended — this release fixes:
- Unauthenticated SSRF, unauthenticated IDOR on customer endpoints, and stored XSS (CWE-918, CWE-639, CWE-79) (#928).
- Unauthenticated account takeover via missing authorization on the customer update endpoint (#952).
- A page-builder security hardening pass (URL/content validation in the editor pipeline).
- Brute-force / credential-stuffing exposure on auth endpoints (covered by the new rate limiter).
- All high-severity dependency alerts cleared (#937).
Performance
- Large-catalog fixes, found by load-testing a 500k-product catalog (#1004): indexed
url_rewrite.request_path(every storefront request resolves URLs against it), fixed a keyword-search query shape that bypassed the GIN index, and removed an O(n²) id-list query-building pattern that also hit the 65,535-parameter wire limit. - Build time significantly reduced (#967–#969); minification is always on.
- SSR render errors now return a 500 instead of hanging the request; GraphQL field errors return partial data instead of failing the page.
- Session-store and boot-path cleanups; the setting cache is warmed at boot for synchronous hot paths (pricing formatter, email helpers).
Bug Fixes
- Built-in
<Form>buttons no longer double-submit (every built-in form used to POST twice). - Google Analytics now loads in production builds.
- Fresh installs no longer log a red
relation "setting" does not existerror on first boot against an empty database. - Migration runner sorts versions by semver correctly (
1.0.10no longer runs before1.0.2) (#961). - Widget types may share component files (settingComponent/previewComponent) — fixed an AreaLoader identifier bug that broke the admin bundle when two types pointed at one file.
cms:pagewidget links resolve throughurl_rewrite(they rendered dead links before).- Cloud-storage image URLs keep their scheme intact in the page-builder image picker.
- Empty settings are no longer saved as the string
"null". - Shipping zone dialog sizing fixed; province selection uses a searchable select.
- Setup no longer crashes with
ValidationError: Progress Plugin has been initialized using an options object(#932); GitHub Actions build no longer fails on the SWC native module. - Dev server boot is hardened against compile failures and file-watcher races.
- Variant option filter guards undefined values; metafield single-field setters fixed (#1006).
- Page-builder fixes: publish keeps you on the current route, the session picker no longer pops mid-session, layers show the right widgets on landing pages, draft-aware widget lookups, drops into empty areas.
Developer Experience
- New public import paths:
@evershop/evershop/lib/urn,@evershop/evershop/lib/metafield,@evershop/evershop/lib/widget/linkResolver, and@evershop/evershop/base/services/sitemap(register your own sitemap collectors). - Two new core modules (
blog,pageBuilder) demonstrate current module conventions end-to-end; the page builder ships with an e2e suite. - Runtime translation APIs and the locale context are available to extensions; admin strings are translatable.
Dependencies
- Added:
@aws-sdk/client-s3,@azure/storage-blob,@google-cloud/storage,express-rate-limit,sonner,undici,fast-json-stable-stringify,@evershop/editorjs-product-list. - Upgraded:
react/react-dom17 → 19,node-cron3 → 4,webpackbar5 → 7,axios1.18,multer2.2. - Removed:
react-toastify,@ckeditor/ckeditor5-build-classic,@ckeditor/ckeditor5-react.
Upgrade Notes
- Back up your database. This release runs 31 migrations, several of which transform data (shipping zones → Core provider, shipment status collapse, widget table rename) and drop legacy columns/tables.
- Update
@evershop/evershop, reinstall dependencies, runnpm run build. Migrations apply automatically on first start. - Re-upload your logo in admin → Store Setting → Branding (
themeConfig.logois gone). Favicon, social banner, and GA4 also live there now. - If your navigation relied on
include_in_nav, rebuild it with the menu widgets. - Review your shipping setup under the new provider-based settings UI — data is migrated automatically, but verify rates.
- For custom themes/extensions: apply the React 19 notes above, switch
react-toastifyimports tosonner, and update anything that touched thewidgettable or legacy shipping tables directly. - Custom cron jobs: verify schedules against node-cron v4.
Credits
Thanks to everyone who contributed to this release: @treoden, @starry-osean (theme public assets, #845), Sang.Tra.