2.4.0
majorBig UpdateFurniture Theme, Central Media Management, Performance & Security
Features
- Added the Furniture Theme — a complete fourth storefront template for home goods sold by the room, with a large hero for room photography, room categories captioned under the picture instead of over it, and a starter home page with a two-panel materials story and "Shop the room" sets that add a whole setting to the cart in one click
- Added a Furniture demo store — 36 products, 8 categories, 2 collections, 3 sliders, and 3 blog posts, with its own home page, header, and footer — imported by the installer or with pnpm db:seed --template=furniture
- Dressed Furniture in its own colours and type — a warm oatmeal page with amber accents and Manrope headings over DM Sans — which Online Store → Themes → Use this theme applies to an existing store, and Theme settings → Colors can change or clear
- Added Central Media Management — the product editor's Media box opens the media library, as Shopify's does, so you search it, filter it to images, videos, or 3D models, tick several files, and add them in one go
- Opened the media library to everyone who builds products — admins see every file in the store, vendors see only their own uploads, and staff who may create or edit products see the store's library unless they are limited to a vendor, location, or region
- Added an Upload button and Add video from URL inside the media library, where a new upload arrives already ticked, and an Add a YouTube or Vimeo video button on the Media box, while drag and drop and paste still upload straight away
- Added a private storage bucket for vendor identity documents, digital products, and expense receipts, set under Settings → Storage → Private bucket, with Test connection checking it and pnpm db:migrate private-storage moving the files already stored
- Added the Footer Studio — build the footer visually from rows, columns, and items, as the Header Studio does, with drag and drop, a property sidebar, and undo and redo, starting from the footer your store already has
- Added Language and Currency items to the Header Studio, a Home page only switch for any header row, and padding and space-below controls for header rows and the announcement bar
- Rebuilt the slider editor around where each slider really appears — it finds every cell that uses a slider and opens on that cell's real size at desktop, tablet, and phone — with a Big tile size for half-width cells, text and button styling in the property sidebar, and an Own gradient over a background picture
- Moved the brand colours into Online Store → Themes → Theme settings → Colors, beside the colour roles that use them, and added a storefront scrollbar setting under Layout — Thin, Standard, or Hidden, with its own colours
- Added Settings → Products, which switches physical products, digital products and services, pre-orders, and price on request on or off in every product editor — admin, staff, and vendor — without changing a product already live
- Added store credit — refund to store credit instead of the card, give credit as goodwill from a customer's page, and let shoppers spend it at checkout and follow their balance under Account → Store credit
- Added final sale for products, variants, or whole collections, shown on the product page and in the cart before the shopper pays, with each order line keeping the mark it was sold with
- Added return windows per collection or per product, or no time limit at all, counted from each parcel or from the order's last one, with every order keeping the return rules it was sold under
- Added exchanges, where a return pays for a new order of other items and the shopper pays any difference through a payment link
- Added returns the store or a seller opens for the shopper, a "Changed my mind" reason, return instructions the shopper can mark as posted with tracking, return label uploads, restocking in steps, and declining a return with a reason
- Added Committed and Unavailable stock to Inventory, so On hand is what a shelf count finds — including units sold but not yet shipped, and returned units that came back damaged, which you can put back on sale or write off
- Added Spend per loyalty point under Settings → Orders, so points follow your currency and one order in a small-unit currency no longer makes a customer Platinum
Performance
- Served the home page and every product page from the cache instead of drawing them again for each visit — a product page answers in about 3 ms instead of 12, one store process serves about five times as many product pages a second, and an edit you save still reaches the next visitor
- Cut the database round trips an order waits on at checkout from 31 to 15, so a cash-on-delivery order with the database 85 ms away is placed in 2.7 s instead of 5.4 s
- Cut reading the cart, which every page does, from 7 database round trips to 3 for a signed-in shopper and from 5 to 2 for a guest, and read the store's settings once per page, where the home page had read them six times
- Drew the product page on the server and loaded only the controls a shopper works as code — 38 KB less JavaScript, and product data a third of its old size
- Sent checkout and account wording, the country list, and confirmation dialogs only to the pages that use them, and the AI assistant only while it is switched on — most storefront pages load 15 KB less compressed JavaScript, and the home page's HTML is 11 KB smaller
- Loaded only the parts of the form validation library in use, toasts only when needed, and the phone-number library only for stores that take a phone number at checkout — the login page loads 81 KB less compressed JavaScript, checkout 104 KB less, and every page 14 KB less
- Compiled translations when the store is built and gave storefront pages a stylesheet without the dashboards' styles — 9 KB less JavaScript and 10 KB (16%) less CSS on every storefront page
- Turned off the React Compiler, which took a third of the build's CPU — a build on a 10-core machine dropped from 21 to 15 seconds, and storefront pages load 19–30 KB less compressed JavaScript
- Prefetched a link when the shopper points at, touches, or focuses it, instead of every link in view — which had sent 18–51 server renders per page view — and showed a thin progress bar the moment a link is tapped
- Brought Back and Forward to the page the shopper just left, as a browser does, instead of fetching the whole page again — about 360 KB for the home page — unless it is more than 30 seconds old
- Refreshed the storefront in the background after a sale, a restock, or a new review, so the next visitor no longer waits on a fresh render — the live demo's home page had taken 7 s at such a moment
- Re-rendered only the product card that was tapped on add to cart, where every card on the page had re-rendered twice, and only the search box while typing in the header search
- Made the account pages keep what they have read, so moving between them no longer reloads each one behind a spinner
- Resized pictures from a custom CDN domain through the image optimizer once STORAGE_PUBLIC_URL is set, where they had been served at upload size — a 110 KB product photo now reaches a phone's card as 19 KB
- Kept cached pages in memory, 50 MB by default, and capped optimized images at 1 GB of disk, so a crawler going through every product in every language can't fill the server's disk — PAGE_CACHE_MEMORY_MB and IMAGE_CACHE_DISK_MB change the limits
Security
- Required INSTALL_TOKEN before the installer creates a new store's first admin, so nobody who finds a fresh deployment before you do can claim it — an installed store needs nothing
- Kept the demo logins to demo deployments, and made the seeders and the reset refuse a real store's database unless you say otherwise
- Counted each visitor by the address your proxy or Cloudflare actually saw, so rate limits, the login lockout, and the maintenance allow-list can't be sidestepped with a made-up header — list any other CDN's ranges in TRUSTED_PROXIES
- Refused every scheduled job while CRON_SECRET is still the .env.example placeholder or shorter than 16 characters, and compared it in constant time — set a long random value before you upgrade
- Signed other devices out on a password change, a password reset, or Sign out other devices, which had removed no sessions at all
- Stopped the email verification link signing an account in, and asked for a sign-in from the last ten minutes before a login email, a first password, or a seller's payout bank account can be changed
- Changed administrators only on the Team page, under its owner and last-administrator rules, and signed a banned or suspended account out at once
- Held a vendor's staff to what the vendor itself may do — no changing a shopper's login email, deleting customers, creating orders marked paid, or touching other sellers' parcels
- Kept staff limited to a vendor or a location inside it — the reviews, customers, returns, and alerts they see are their scope's alone, and the shared global variants and the whole site's traffic analytics are out of their reach
- Kept your cost prices, commenters' email addresses, your internal notes about shoppers, and a guest cart's contact details out of everything the storefront sends
- Made password-protected blog posts private, since the option had never protected them, and stopped the blog API sending a post's password
- Attached a guest's orders, quotes, and points only to an account whose email is confirmed, never by phone number, and kept each sales-assistant chat with the shopper who had it
- Printed what people type as plain text in your store's emails, and built payment return, checkout recovery, and subscription confirmation links from your store's own address, never from what a request claims
- Applied request limits to admins, vendors, and staff too — at twenty times a shopper's — and to guests by address, where a fresh cart cookie had skipped them
- Made a password-reset link work once, a download limit hold when requests arrive together, and password reset stop revealing which emails have an account
- Let only the admin delete stored files — from the Media Library, media only, with each deletion in the audit log — and gave uploaded files unguessable names
- Limited shopper uploads to photos — JPEG, PNG, GIF, WebP, AVIF, or HEIC, 10 MB each, four at a time — signed the file type into direct-upload links, and never store an uploaded SVG raw by accident
- Stopped the image optimizer, the 3D model preview, the AI studio, and the invoice logo following redirects away from your storage, and served 3D previews only as .glb, .gltf, or .usdz models
- Escaped a vendor's own text in their store page's structured data, stopped other sites framing your store, and sent nosniff, a stricter referrer policy, and HSTS in production
- Answered unexpected server errors with "Something went wrong on our side" and a reference in the log, instead of a raw error naming your database host, bucket, or file paths
- Settled cash the store's courier collects only by the courier's update or an admin, required a register card sale to use the payment its own register took, and closed downloads on a line refunded in full
- Marked the guest cart cookie Secure over HTTPS, kept reset and recovery tokens out of analytics, and kept a guest checkout away from an account's saved cards
- Updated Next.js to 16.3.6 for CVE-2026-94545 and email to Nodemailer 10, and removed an unused AI endpoint that let any signed-in shopper spend your OpenAI key
Improvements
- Opened Settings inside the dashboard, where the sidebar swaps to the settings menu — with search, Unsaved and needs-attention markers, and Back to Dashboard — and kept one settings page's unsaved edits when another page is saved
- Asked before leaving unsaved changes on Vendors → Configuration, Branding, and live chat, as the rest of Settings already did
- Picked one Store Currency under Settings → General from every ISO 4217 currency, and removed settings that nothing read — the time zone, the POS language, and the vendor auto-approve and free-trial-length fields
- Made dashboard preferences — light or dark, contrast, right to left, and the sidebar — each person's own, where an admin switching to dark mode had turned the storefront dark for every shopper
- Translated the settings screens and the admin, vendor, and staff menus into all 18 languages — about 5,500 strings added and 2,500 replaced — naming each language in its own script and each currency in the dashboard's language
- Extended the product page editor to the whole page — show or hide the section tabs, a pinned buy bar that follows your button settings, delivery-info styling, and tabs only for sections the page shows — and removed the placeholder FAQ row
- Defaulted product thumbnails to 160 px with their own background and fit, kept the Carousel gallery above the buy box at every width, let product cards outline their image, and gave every admin colour field the same picker
- Let shoppers who share an address — a mobile carrier, an office, a school — keep browsing and buying, with browsing limits raised from 100 to 1,000 requests per 15 minutes and a checkout limit per shopper, and told a refused visitor how many minutes to wait, in their language
- Answered 404 for a product, category, collection, brand, or seller page that isn't there, where it had answered 200 with a not-found page
- Kept admins, vendors, and staff out of the Customers list and its totals, which had counted the team's own test orders as customer spend
- Let barcode scanners add to the POS cart wherever focus is, refused a scan while a dialog is open, and stopped a scan into a product's Barcode or SKU field saving the half-filled product
- Rebuilt Orders → Quotes like the Orders list — stats, tabs by stage, search, filters, pagination, and a side sheet with the full request, its price history, and your team's note
- Redrew the boost position ladder as a booking calendar with each booking on a dated track, and gave admins and vendors one three-step booking dialog
- Added list and card views, drag-to-reorder, and a Vendor view preview to Vendor Plans, with Delete saying why a plan in use can't be removed
- Added a stats strip above the admin and vendor returns lists — needs review, on the way back, refund due, and refunded in the last 30 days
- Made pnpm build run on Windows as well as macOS and Linux, with no memory settings to tune and no database connection needed while building
- Added one hourly job, /api/cron/store-credit, and two migrations only some stores need — private-storage once you add a private bucket, and store-credit-indexes with MONGODB_AUTO_INDEX=false — see the upgrade guide
Fixes
- Fixed switching back to the store's default language doing nothing once a visitor had picked another language
- Fixed the collection and brand pages' sort dropdowns changing their label but not the order
- Fixed a dropped database connection failing every query until the store was restarted — it now reconnects
- Fixed a database blip, or a build without a database, caching the default store — named Storify, in USD — in place of your store's own data
- Fixed a paused or re-dated coupon's banner lingering on the storefront, and a reinstall on a running server keeping the previous store's catalogue, menus, sliders, and coupons
- Fixed a fresh demo store's countdown offers having already ended, which left its home page without the deals panel — they now start from the day of the import
- Fixed a slide image picked from the media library breaking within the hour on a bucket with no public URL, and a custom storage domain typed without https:// breaking every new upload's address
- Fixed a recovery email the outbox was still retrying being marked failed, and recovery emails to shoppers without an account going out without a working unsubscribe link
- Fixed an expired order's payment link still taking payment after the same cart was bought another way
- Fixed signed-in shoppers never hearing that a payment failed, a pay link paid while signed out sending no confirmation, and a pay link opened both signed out and signed in failing with a Stripe error
- Fixed Razorpay's payment window being impossible to click or type in from the vendor's boost, plan renewal, and commission dialogs
- Fixed a card the gateway account won't take, such as a foreign card on a domestic Razorpay account, counting toward a gateway outage alert — the shopper is now told to try another card
- Fixed the refund dialog opening on "Refundable: $0.00" for a fully refunded order, and offering refunds the server then refused
- Fixed a refund above what a split cash-on-delivery order collected, a seller-collected cash-on-delivery refund booked as the store's, and a cancellation whose refund failed without anyone being told
- Fixed staff scoped to a vendor seeing 0 on every order counter while the list below showed their orders
- Fixed a full refund with restock putting a return's damaged units back on sale, and an edit to Available being dropped on save or writing the store-wide figure into the selected location
- Fixed opening the size guide, or picking a variant whose pre-orders are full, blanking the whole product section while that part loaded
- Fixed demo sliders drawing every size 27% too large, artwork scaled past 100% not growing, and a long word in a narrow slide running off its edge
- Fixed a full-bleed hero stopping ten pixels short of the window beside the scrollbar, and category tiles stretching on full-width rows with no tile width to hold them
- Fixed Korean being offered with no translation, Dutch being impossible to switch on, and menu labels that meant something else in Afrikaans, German, Igbo, Yoruba, Xhosa, and Zulu