Configured Commerce Upgrade Watch: March 2026
sts 5.2.2603.389+sts Covers March 2026
Written by Lance Farquhar April 23, 2026
Release 5.2.2603.389+sts, published April 21, 2026. Each month we read what Optimizely says shipped, then go verify it against the actual source — frontend and backend — so you get more than a changelog recap.
The 30-Second Version
- Headline feature: Commerce Search v3's fragile filter-repair-and-retry mechanism is gone. In its place is a shared exception type and a clean fallback to Elasticsearch — filed as a Breaking Change, and it earns that label.
- Worth knowing: Cart Version now defaults to V2 for new sites, and Marketing Lists (the
WishListentity, under the hood) picked up real Start/End activation dates wired through the migration, entity, and query layer. - One more notable item: Recently Purchased List can now scope to the individual user instead of the whole company, and product badges gained a Category-Level Rule so badge assignment no longer has to route through a generic custom-property workaround.
- Our verdict: a clean month. Nearly every highlighted item traces to a small, purpose-built change, and the one item worth reading closely — the search rewrite — turns out to explain three separate release-note bullets at once.
The Headline: Commerce Search v3's Retry Loop Becomes a Clean Fallback
Optimizely filed this one as a Breaking Change with bug-fix language attached — "duplicate products in search results," "unhandled errors resolved," "Google Search integration search errors fixed." Read as prose, those sound like three unrelated fixes. In the code, they're one refactor.
Before this release, when Google Cloud Retail Search rejected a filter because it referenced an attribute it didn't recognize, OptiCommerceSearchSearchClient.cs caught that failure, handed the filter to a static FilterParser, cached the bad attribute name for ten minutes, and recursively retried the same request. If it couldn't work out how to strip the offending attribute, it threw a raw, unhandled Exception.
This release deletes that mechanism outright — FilterParser.cs (232 lines) and its test file (104 lines) are both gone — and replaces it with a shared exception type and a fallback path:
InvalidAttributeSearchException moves from an internal type in Insite.Search.Shared to a public one, Insite.Core.Plugins.Search.InvalidAttributeSearchException, thrown from 2700_PerformAggregationAndSearch.cs when the HTTP client reports an "Unsupported field" error. The storefront's own 200_GetSearchResults.cs catches it two layers up and drops to Elasticsearch entirely:
// 200_GetSearchResults.cs
parameter.ProductSearchParameter.Relevancy = false;
A git show against the parent commit confirms the old cache-and-retry mechanism — FilterParser, the ten-minute cache, the recursive retry — existed unchanged going into this release, so this is a genuinely new-this-cycle removal, not stale carryover from an earlier month. It also lines up with why Optimizely filed it as a Breaking Change rather than a plain bug fix: the old retry path could re-issue and re-merge search calls (which explains the duplicate-products symptom), and any site depending on that auto-repair behavior — even unintentionally — will now see different results the moment a filter hits an attribute Google Cloud Retail Search doesn't recognize. Instead of a retried, patched-up search, it gets Elasticsearch relevancy instead.
What Else Shipped
The one breaking change this month is the Commerce Search v3 rewrite covered above. If you run Google Cloud Retail Search with custom attribute filters, that's the item to test before rolling this release to production.
- Recently Purchased List can now scope to the individual user instead of the whole customer/company — a new setting on
OrderHistorySettingsfilters order history lines down to the current user. - Category-Level Rule for product badges lets a badge rule match
ProductCategorydirectly, alongside the existingObject/CustomPropertycriteria types — no more routing category-based badge logic through a workaround. - Marketing Lists (the
WishListentity) picked up Start/End activation dates, touching the migration, entity, query, and Admin entity-definition layers. Covered in more detail below. - Automatic translations for product details reached general availability, and the same rework to
EntityTranslationServiceappears to be what fixed the Product UrlSegment non-translatable property error — non-translatable properties likeUrlSegmentare now populated directly from the entity accessor and skipped from the translation lookup, instead of raising an exception when requested. - Custom properties from the Application Dictionary are now selectable as Admin Console grid columns and filters, backed by a new
custom-property-columns.tshelper. We didn't trace the filter-UI wiring end-to-end, so if you lean on this heavily, it's worth a pass on a running instance. - Audit log retention is now automated — a configurable
AuditLogRetentionDayssetting (default 90) drives a new purge step in the nightly maintenance job, and a new index on the audit table's date column backs the reported performance improvement. - PDF generation picked up IronPDF-based retry logic, with new configurable network-idle and render-delay settings.
- Email attachments over 20 MB are delivered as download links instead of failing, via a new signed-URL endpoint with path-traversal guards built in.
- Cart Version now defaults to V2 for new sites, and the
ISC_Implementerrole gained access to that setting alongsideISC_System— a quiet change, but one that changes default behavior for every new implementation from here on. - Payment Service picked up a Verify transaction type, and payment-profile expiration-date parsing is now tolerant of malformed input instead of throwing.
- Newsletter signup is now available directly on Checkout in Spire CMS and One Page Checkout, and the Add Page option is hidden on Spire CMS system pages where it never applied — product/brand listing and detail pages.
- Segment-based headers in Spire CMS now render correctly — the page-repository join now consults page state instead of skipping it.
- A handful of smaller fixes check out cleanly: VMI location-ID filtering now returns an empty result instead of an error (detail below), PunchOut orders are correctly tagged with their source (detail below), Product Recommendation feed URLs are fully qualified for the CDN, the Shared By indicator now displays correctly on shared lists, and one-page checkout now locks billing-address edits once a quote has been accepted, preventing conflicting changes mid-flow.
- Two items didn't turn up in either repo — the Application Insights database-tracking toggle and the Recommendations child-variant tracking fix. Both read like the kind of change that could live in settings or configuration outside the diffed source. Worth testing directly against a running instance rather than assuming either one is missing.
What We Actually Found: A Quiet Move to Vite for Admin Console Dev
Every post in this series, we go looking for something the release notes don't mention. This month it's a piece of internal tooling work.
AdminApp.cs picks up a new using Vite.AspNetCore; and, in Development builds only, wires up AddViteServices — pointed at port 30079 and the Insite.SystemResources/Modules/AdminConsole folder — alongside UseWebSockets() and UseViteDevelopmentServer(useMiddleware: true). Vite.AspNetCore was also added to allowedLibraries.netcore.json, which is what separates this from stray experimental code — it's a deliberate, tracked addition.
None of it touches production behavior; it's gated entirely behind the development environment check. What it signals is a migration of the Admin Console's local dev experience off the older webpack/bundle pipeline and onto Vite's module server — a change that mainly affects how fast a developer's changes show up in the browser while working on Admin Console code, not what ships to customers. The same commit adds a small piece of routing hygiene alongside it: a middleware that lowercases and redirects any mixed-case /Admin... request to /admin..., so an inconsistently-cased link or bookmark into the console won't quietly 404.
None of this appears anywhere in the published release notes. If you maintain custom Admin Console modules, it's worth knowing your local dev setup for that area is changing, even though nobody wrote it down.
Under the Hood
Marketing Lists' Start/End dates are wired through every layer, not just the Admin UI. "Marketing Lists" in the Admin Console is, in the codebase, the WishList entity — worth knowing if you're cross-referencing by class name. A new migration, 2026.03.06.01.AddWishListActivationColumns.sql, adds ActivateOn/DeactivateOn (datetimeoffset) to [dbo].[WishList]; WishList.cs implements IArchivable<WishList>; and the query itself does the filtering:
.Where(o => (o.ActivateOn == null || o.ActivateOn < current)
&& (o.DeactivateOn == null || o.DeactivateOn > current));
wishList.json flips IsArchivable: true to match. Migration, entity, query, and Admin entity-definition JSON all changed together for one feature.
Expired promotion codes get their own message with a single new conditional. 600_ValidatePromotionCode.cs now checks expiration before falling back to the generic invalid-code path:
this.IsPromotionCodeExpired(unitOfWork, parameter.PromotionCode)
? MessageProvider.Current.Promotion_Expired
: MessageProvider.Current.Promotion_InvalidCode
IsPromotionCodeExpired() queries IPromotionRepository for a promotion matching the code whose DeactivateOn has already passed. One conditional, one new message — not a new validation pipeline.
VMI location-ID filtering no longer throws on bad input. A non-GUID vmilocationid or vmibinid used to raise an ArgumentNullException deep inside GetOrderCollectionMapper.cs. Now it falls back to an empty GUID:
// GetOrderCollectionMapper.cs
parameter.VmiLocationId = Guid.Empty;
An empty GUID naturally produces an empty result set — "No orders found" — instead of surfacing as an unhandled error.
PunchOut orders are tagged at the point they're created. 200_CreateCustomerOrder.cs now sets the order source explicitly:
// 200_CreateCustomerOrder.cs
OrderSource = CustomerOrder.OrderSourceType.Punchout,
Before this, PunchOut orders were indistinguishable from ordinary web orders in reporting. One field, set once, at creation.
What This Means for You
- If you run Commerce Search v3 with Google Cloud Retail Search custom attribute filters, re-test filtered searches on this release before rolling it to production — the fallback to Elasticsearch relevancy is new operational behavior, not a defect.
- If you're standing up new sites, know that Cart Version now defaults to V2 — worth a line in your own release notes if you hand those to clients.
- If you use Marketing Lists (the
WishListentity) for time-boxed campaigns, the new Start/End dates are ready to use in Admin Console with no extra setup. - If you depend on the Application Insights database-tracking toggle or child-variant Recommendations tracking, verify both directly against a running instance — neither turned up in either repo diff.
- If you maintain custom Admin Console modules, expect the local dev tooling underneath to keep moving toward Vite, even though production behavior is unaffected.
- Everything else — the promotion-expired message, VMI filtering, PunchOut order source, and the rest of the listed bug fixes — is safe to take as-is.
Upgrading a Configured Commerce storefront and want a second set of eyes on what actually changed under a release before you take it? That's exactly the kind of review Nishtech does for clients every month — reach out and we'll walk through it with you.