We just shipped EverShop 2.2.1 — the largest release since 2.0. It folds in the React 19 work that had been sitting in an unpublished 2.1.3 branch and stacks four months of development on top of it: a visual page builder, a blog module, entity custom fields, a multi-language storefront with a translated admin, a rebuilt shipping and fulfillment stack, built-in cloud storage, product recommendations, and a serious security and performance pass.
If you're upgrading an existing store, one number to keep in mind: 31 database migrations across 10 modules run automatically on first start. Several of them transform data and drop legacy tables, so back up your database first and read the breaking-changes section below. This release also patches several security vulnerabilities, so upgrading promptly is the right move.
Here's a tour of what's new, and what you'll need to change if you maintain themes or extensions.
Visual Page Builder
The headline feature is a drag-and-drop editor for the storefront, living at /admin/page-builder.
You edit any storefront route — plus CMS pages and landing pages — by composing widgets into your theme's areas, with layout-aware drag/drop. The workflow is draft-based: changes accumulate in a per-admin, per-theme draft changeset with per-widget auto-save. When you're ready you can publish immediately, or schedule a rollout for later — and those rollout plans stay editable and cancelable right up until they run.
There's inline editing on the canvas (text and images edited in place, with an image picker that understands cloud storage), a layers panel, a "Globals" view for site-wide areas, and per-widget styling controls. Link fields resolve products, categories, CMS pages, and blog posts through a single unified link resolver.
Because it's touching public-facing content, the whole editor pipeline went through a dedicated security-hardening pass and ships with an end-to-end test suite.
Blog module
EverShop now has a first-class blog core module: posts, categories, and tags, with per-post SEO descriptions, comments, and reactions. You get storefront blog pages out of the box plus blog widgets for the page builder. No more bolting a separate CMS onto your store just to publish articles.
Metafields: typed custom fields on every entity
This is the feature extension authors will get the most mileage from. You can now define typed metafields — text, long text, rich text, number, boolean, date, select, JSON/group, and list variants — and attach them to products, categories, collections, customers, orders, and the shop itself.
Values are stored per entity in a JSONB meta_data column and validated with AJV on write. GraphQL exposure is audience-gated, so you decide whether a field is customer-visible or admin-only.
The part worth calling out for theme developers: a theme can declare its own metafield definitions in theme.json, and they get provisioned automatically at theme activation and on boot. You render values with a new storefront component:
<Metafield entity={product} name="care_instructions" />
As a nice piece of dogfooding, the footer copyright line is now itself a shop metafield you can edit from the page builder.
Multi-language storefront and a translated admin
Localization is now runtime-based — no rebuild required. Per-locale CSV dictionaries live in your project's translations/ folder, with English as the source language. Enabled languages and the default language are admin settings; non-default locales get URL prefixes like /de/... with canonical handling, and REST APIs accept an X-Locale header.
The admin panel has its own language, independent of the storefront, and translations ship bundled for 17 locales.
One React 19 gotcha this surfaced (more below): don't call the translation helper _() at module scope, or you'll freeze the string at import time and trigger a hydration mismatch. Call it at render time, inside the component.
Rebuilt shipping and fulfillment
The old flat "zone → method → rate" model has been replaced with a provider abstraction. Shipping rates now come from registered providers; the built-in Core provider reproduces the classic setup (flat, percentage, price/weight-based, and API-calculated rates), and extension providers can quote live rates with configurable per-provider timeouts.
Fulfillment went from one-to-one to multi-shipment: an order can ship in several packages, each shipment carrying its own item assignments, status, tracking, and optionally a purchased label. order.shipment_status becomes an item-based rollup over physical items (digital items excluded).
There's also a carrier registry — register carriers that create labels, generate tracking URLs, and push status updates back into EverShop — and package management for admin-defined parcel sizes with dimension/weight snapshots that flow from cart_item → order_item → carrier requests.
Your existing zones, methods, and rates are migrated into the Core provider automatically. If you had custom code querying the legacy shipping tables, it'll need to move to the provider registry.
Cloud storage, built in
S3, Azure Blob, and Google Cloud Storage are now part of core (they used to be separate extensions). You pick and configure a provider at runtime from a new System Setting page — no restart — and secrets are masked in the admin. The rewrite also fixed a batch of defects from the old extensions: regional S3 endpoints (plus custom endpoint / path-style for R2 and MinIO), correct Content-Type on upload, listing beyond 1,000 keys, and Azure public-access handling.
Product recommendations
Three recommendation systems shipped together:
- Related Products — rule-based (same category / collection / attribute values, with price bands, priority ordering, and manual picks), configurable globally, per category, and per product.
-
Frequently Bought Together — driven by co-purchase statistics (association confidence + lift with configurable thresholds), rebuilt nightly and on demand. It also reaches the cart page as
Cart.crossSellProducts. -
Upsell shelf — derived automatically from the related-products rules restricted to pricier products, exposed as
Product.upsellProducts.
New GraphQL fields (Product.relatedProducts, Product.crossSellProducts) and three page-builder widgets render the shelves with full stock/visibility gating and variant awareness.
Platform and operations
A grab-bag of infrastructure work that quietly matters at scale:
-
Per-IP rate limiting is built in: ~300 req/min on pages, 120 req/min on APIs, and 8 attempts per 15 minutes on login/registration/password-reset. It honors
TRUST_PROXY_HOPSfor correct client IPs behind proxies. -
Auto-generated
sitemap.xmlwith a collector registry so extensions can add their own URL sources, multi-languagehreflangalternates, and a dynamicrobots.txt. - Automatic URL redirects: renaming a product/category/CMS/landing-page slug captures a 302 redirect from the old path, so old links and bookmarks keep working.
- Landing pages built entirely in the page builder, served at root-level friendly URLs.
The React 19 upgrade (and what it means for your code)
EverShop moved from React 17 to React 19. Because the framework resolves React through a single hoisted copy (a webpack alias), every extension and theme now runs on React 19 too. If you maintain either, budget time for these:
defaultProps on function components is gone — use ES default parameters instead:
// Before
function Price({ amount }) { /* ... */ }
Price.defaultProps = { amount: 0 };
// After
function Price({ amount = 0 }) { /* ... */ }
String refs, findDOMNode, legacy ReactDOM.render / ReactDOM.hydrate, and legacy context are removed. Use callback/useRef refs, createRoot/hydrateRoot, and createContext. Read client-only state (like window.location) in a useEffect, not in a useState initializer, so the first client render matches the server.
One more breaking change worth flagging: react-toastify was removed in favor of sonner. The migration is close to drop-in:
// Before
import { toast, ToastContainer } from 'react-toastify';
// After
import { toast } from 'sonner';
// and replace <ToastContainer/> with sonner's <Toaster/>
Other breaking changes to skim before upgrading: the shipping/fulfillment rework (above), a widget storage refactor (widget → widget_instance plus a widget_placement table), several config keys moving from config.json to admin settings (notably themeConfig.logo is gone — re-upload your logo in the admin after upgrading), and category.include_in_nav being dropped in favor of menu widgets.
Security and performance
On security, this release fixes an unauthenticated SSRF, an unauthenticated IDOR on customer endpoints, stored XSS, and an account-takeover path via a missing authorization check on the customer update endpoint — plus clearing all high-severity dependency alerts.
On performance, we load-tested a 500k-product catalog and fixed the things that fell over: indexed url_rewrite.request_path (every storefront request resolves URLs against it), fixed a keyword-search query that was bypassing the GIN index, and removed an O(n^2) id-list pattern that was also hitting Postgres's 65,535-parameter wire limit. Build time is down significantly, and SSR render errors now return a clean 500 instead of hanging the request.
Upgrading
- Back up your database — 31 migrations run on first start, several destructive to legacy tables.
- Update
@evershop/evershop, reinstall dependencies, runnpm run build. Migrations apply automatically. - Re-upload your logo under Store Setting → Branding.
- If your navigation relied on
include_in_nav, rebuild it with the menu widgets. - Review your shipping setup under the new provider-based UI — data is migrated, but verify your rates.
- For custom themes/extensions: apply the React 19 notes, switch
react-toastifytosonner, and update anything that touched thewidgettable or legacy shipping tables directly.
Full release notes: https://evershop.io/blog/release-notes-v2-2-1
Changelog: https://github.com/evershopcommerce/evershop/releases/tag/v2.2.1
EverShop is open source and MIT-licensed. If you build something on it, or hit a snag upgrading, come find us on GitHub — issues and PRs welcome. ⭐
Top comments (0)