
Where we started
Both implementations were current before the jump. Both were on CMS 12 and Commerce 14, both used Search & Navigation (Find), and both were hosted on DXP. The enterprise site was still on .NET 6; the standard site was already on .NET 8.
One thing CMS 13 got right immediately was version alignment. Optimizely renumbered its satellite packages so everything lands on 13.x.
“Some of the things that weren’t numbered in alignment became aligned. Something that might have been like 4.0 before is now 13.0, which I actually think will be good going forward.” — Caleb
The tradeoff is that everything now bumps whether or not it changed. Clay decompiled a handful of 13.1.1 packages and found them identical to their predecessors apart from the version number.
What genuinely went well
The core platform move was the least dramatic part of the project.
“I thought a lot of the basic CMS stuff and .NET 10 stuff transferred pretty well. There were a couple things that got deprecated or obsoleted, but it was usually relatively simple to find the one-for-one upgrade.” — Caleb
The sites-to-applications change is a good example. SiteDefinition is gone, replaced by an application model intended to support headless and mixed deployments under one management story. The enterprise site is multi-site, so this impacted the upgrade slightly.
“That was a pretty easy change. It’s almost like namespace changing… By and large, you just had to go and find and replace, more or less, in a few places.” — Clay
Clay’s broader read on the intentional architectural changes was positive: they were thought through, and there was usually a documented before-and-after once you located it. The one sharp edge was that some of these changes compile clean and fail quietly at runtime.
“It still compiled and it still worked. It’s just getting routed randomly to the wrong site sometimes because of the way we were using it before. And it took a little bit to figure out why.” — Clay
Performance also recovered. Clay found the upgraded solution painfully slow at first, but as newer package versions landed it came back to roughly where it had been — with a noticeably heavier resource appetite. He’s watched Visual Studio’s debugger sit above eight gigabytes of RAM, which he’d never seen on this codebase before.
The AI upgrade tooling didn’t carry the load
Optimizely’s upgrade guidance now points teams at GitHub Copilot’s app modernization agent, since the previous Optimizely and Microsoft upgrade tools were deprecated.
Clay got value out of it, mostly on generic .NET work rather than Optimizely-specific migration. It handled the move to central package management well — a change both developers flagged as a genuinely useful side benefit. But it overreached.
“It tried to fix a bunch of things that it didn’t need to fix, or that I didn’t want to fix. It got to a point that I was happy with, and then it kept going.” — Clay
His summary is the useful part for anyone planning to lean on tooling: “It’s really just a generic upgrade tool. In theory it’s universal — it has no Opti context.”
There’s also a timing problem no tool can solve. When half the ecosystem hasn’t shipped a compatible version yet, automated upgrades have nowhere to go.
“When we started the upgrades, they hadn’t released them yet. Service API is one of the biggest ones. That probably hindered any automatic upgrade attempts, because things just didn’t have upgrades. And then you end up with a bunch of package requirement mismatches.” — Caleb
Dependency untangling
CMS 13 has a hard dependency on Commerce 15, so they move together — and they didn’t arrive together. Clay tracked the dates: the first non-preview CMS 13 package landed on March 31; the first non-preview Commerce package didn’t arrive until May 18. We started in between.
Package version constraints cut both ways. Some add-ons pin a maximum version, which breaks a major upgrade loudly:
“Suddenly you run afoul of those, and you end up having to manually specify a lot of packages instead of being able to rely on transitive package dependencies.” — Caleb
Others are too permissive, which is worse:
“Some were too lenient — greater than 12.3 or something, with no upper bound. So those kind of quietly stuck around despite being incompatible. That led to a bunch of runtime issues down the road that were a pain.” — Clay
The real project: Search and Navigation to Graph migration
Ask either developer where the time went and the answer is immediate.
“Graph was absolutely the biggest part.” — Caleb
“We’ve really hit on a lot of the Graph stuff, and that’s where probably well over half of our time has been spent on in this upgrade.” — Caleb
Lesson one: don’t try Graph on CMS 12 first. Both teams attempted it, reasoning that decoupling the search migration from the platform upgrade would de-risk both. It didn’t work. Caleb got article search running, but then products wouldn’t index correctly.
Clay’s conclusion:
“It’s nothing but pain on CMS 12, from our experience. If you want to go to Graph, just go to 13 and do it all together.” — Clay
Lesson two: Graph isn’t a replacement for Find: it’s a different tool. Find was purpose-built around Optimizely content, with interfaces that were pitched as broadly implementation-agnostic. Clay had expected some of that continuity to survive.
“The whole sales pitch was that there are very standard search interfaces and APIs that are as much as possible supposed to be implementation agnostic, so you can write search code that is interchangeable… I thought they were going to try to keep more of those APIs standard and stick with that theme.” — Clay
Graph follows GraphQL, and that shapes everything. It excels at precise, headless-style querying — ask for exactly the fields you want. Existing server-rendered implementations tend to be built the opposite way.
“So much of what we had was ‘give me everything,’ and then I may or may not use it later down the line in my code. And Graph from the start wasn’t really designed for that.” — Clay
Early on that meant hand-building POCO (Plain Old CLR Objects) models for every query, which was both a large amount of work and a bet that might not pay off.
A later addition — loading results back as content rather than into custom models — materially improved this:
“It got a lot easier with the addition of ‘get as content,’ so we could just load the content in. Which maybe isn’t the most efficient, but it much more closely resembled our existing flow.” — Caleb
Even where features exist on both sides, the syntax churn is constant: “filter” instead of “where,” small renames that are trivial individually and add up across a large search codebase.
Lesson three: “there’s an equivalent” is doing a lot of work in that sentence.
“Features that they claim there are equivalents for… they’re not plug-and-play swappable. Feature by feature, they all at least work differently, if not are outright completely different — which for existing solutions is really difficult.” — Clay
Concretely, from Clay’s inventory:
- Autocomplete and “did you mean” — autocomplete in Graph is per-field rather than per-term, which is a real problem when a single search spans dozens of fields, as it does on the enterprise site: “how do you collate all that?”
- Boosting — “works completely differently,” and auto-boost based on result popularity has no equivalent.
- Synonyms — closest to one-to-one, with a viable migration path.
- Best bets — now pinned results. “Kind of, sort of… You can kind of get there. They’re just not the same.”
- Search tracking and statistics — click tracking is now client-side, which is very different from how our implementations were designed.
Optimizely Search and Navigation upgrade challenges
This is where the enterprise implementation diverges sharply from a typical build, and it’s the best answer to “why was this so much harder for them?”
That site used Find as a general-purpose fast-read layer, not just a search index.
“It’s not just products and pages. We use it for a handful of things just because [the site] is so big that they’ve wanted more and more optimization… Even things like orders, some of that information is in Find, because we had certain accounts that had multiple thousands of orders on one account. So our usual implementation would slow to a crawl.” — Caleb
Every one of those became its own migration.
“We just had so much Find usage — which, if making the conversions is already difficult, having to do it more, and doing these not-intended basic use cases, it all adds up.” — Caleb
Caleb’s read on the split between the two projects: “Clay definitely got the short end of the stick on that one. The enterprise site just has so many more customizations where they wanted it done a particular way.” On the standard commerce site, some of the same changes are still “flying under the radar” simply because the features aren’t used as heavily.
Customizations, internal APIs, and the cost of going off-road
CMS 13 tightened API visibility, and that landed hard on a deeply customized implementation.
For years, Optimizely marked certain classes as internal by convention — documented as “don’t depend on this” while remaining technically public. Our team, like many, decompiled and extended some of them to deliver requested behavior.
“It said don’t use it, but you were allowed to. And admittedly, when you re-implement an internal class like that, it does feel a little bit cheaty. But if you’re allowed to, and it gets what our client wants, well, then I guess we do it.” — Caleb
In CMS 13, many of those became inaccessible, and the dependency chain compounds the problem.
“Those decompiled classes tend to really heavily reference other Opti classes. So when they marked a bunch of those internal, suddenly it gets a lot harder for us to implement our replacement, because we don’t have access to everything it was calling.” — Caleb
“The features were there and, lo and behold, the stuff that could change changed.” — Clay
Casualties on the enterprise site included a set of admin-area customizations — a rebuilt contacts and organizations menu that had to be reverse-engineered to reach feature parity — and a developer convenience that fell back to a remote blob store when images weren’t present locally, which was simply scrapped.
A smaller-looking change with outsized impact: ContentArea.FilteredItems was removed, along with a third-party content area rendering package (used for Bootstrap-style layouts within content areas) that has no CMS 13 successor. The filtering that the old API did implicitly now has to be done by hand, everywhere it was used.
“It’s ultimately a good change, like a lot of these, and for the right reason.” — Clay
Waiting on the ecosystem
Beyond Graph, the recurring pattern was that the platform shipped before the things built on it.
Service API is the clearest case. It didn’t have a CMS 13 release when we needed it; the packages arrived roughly a month ago. Functionally it’s much closer to its predecessor than Graph is to Find — but the endpoints all changed, so every consumer still needs manual updating. A companion OpenID package it had depended on was discontinued, and both projects had customizations built on its auth code.
“There were places where we’d written our own REST API type equivalents for some of those things, and that was relying on code that was in there — like the auth code. And suddenly that code’s not available to reference.” — Clay
That package reappeared the day before our conversation, which captures the current state of things well:
“Clay says it’s back as of yesterday, but I haven’t seen it. And I don’t know if we even need it anymore.” — Caleb
Some Optimizely Labs packages are also still outstanding, which both developers considered more reasonable given their experimental framing.
The project-management effect of all this is worth naming, because it’s what customers actually feel:
“They’re pushing CMS 13 — it’s ready, go out and develop. But we have found we’re definitely getting ahead of it on multiple occasions. And then we’re stuck in a little bit of limbo. It’s like, all right, do we need to make a workaround, or are you guys coming out with this next week, month, whenever?” — Caleb
“You’re getting asked, ‘When’s it going to be done?’ And it’s one of those things where it’s like, well, what is done? I can take this out for now, but I know we had this piece before and you’re going to want it. So it can’t be done done.” — Clay
And the conversation nobody enjoys:
“And to go back to the client with it too, and be like, all right, well, we did the upgrade, but that feature you really liked…” — Caleb
Documentation was a moving target
Asked what else needed to be said, Clay’s answer wasn’t technical.
“The documentation’s really been a moving target. It’s one thing when the URLs change and things get moved around and it becomes hard to find stuff you’d found before. But when the information changes and the recommendations change, or you’re trying to implement something that isn’t documented and then later becomes documented, it’s hard not to feel like it was wasted time.” — Clay
“It makes it seem kind of haphazard. And you’re being asked questions about things that you don’t know — you can’t look it up, because it’s not there. So you’re just having to guess, and that’s not great either.” — Clay
His concrete ask is a reference implementation, available at launch rather than months later:
“Having a reference library, a reference example would have been really helpful. Here’s how it used to look on Foundation, here’s how they migrated it, here’s how it should look now to have the same features. That would have been helpful.” — Clay
He noted the Foundation CMS 13 work appeared to stall for a couple of months and still felt incomplete: “I appreciate the attempt, but having that from the get-go would have been helpful too.”
Where things stand
Both implementations are in test environments; neither is in production. The standard commerce site has deployed to DXP integration and that path looks solid so far — content graph keys updated, one Service API fix, otherwise straightforward. Clay hasn’t reached that stage on the enterprise site but is cautiously optimistic based on the deployment documentation, which he considers well thought out.
What we’d tell another team
- Do Graph and CMS 13 together. Attempting Graph on CMS 12 cost both projects real time for nothing.
- Inventory Find usage beyond search first. Every place Find was used as a fast-read shortcut is a separate migration.
- Audit feature parity individually. “There’s an equivalent” rarely means drop-in. Check autocomplete, suggestions, boosting, best bets, and tracking against your actual requirements before committing to a date.
- Plan to test packages that have permissive version ranges (> 12.x). These let incompatible assemblies through restore and surface as runtime failures much later.
- Expect to pay for going off-road. Some decompiled and internal-API-dependent customizations had to be reworked.
- Don’t count on AI tooling for the Optimizely-specific work. It’s fine for .NET-level mechanics and package hygiene, but it has no platform context.
The honest bottom line
The core platform move was straightforward — .NET 10 and most CMS APIs carried over, and deprecations usually had a documented one-for-one replacement once the right page turned up. Optimizely followed through on what it tracked publicly: product recommendation tracking shipped within the timeline they had set for themselves, Service API landed, and the discontinued OpenID package reappeared. Graph itself improved — being able to load results back as content, rather than into hand-built models, brought the new search code much closer to the code it replaced. Performance, which looked alarming immediately after the upgrade, recovered as the project continued and packages were upgraded, and the DXP deployment path has so far been smooth.
More broadly, the architectural direction holds up. Putting every package on a single 13.x version, replacing site definitions with the applications model, even removing convenience APIs that concealed expensive work behind a simple property call: these look like changes that improve the platform. Clay’s read:
“A lot of the direction, for the things I can see, makes sense, and I can understand why they’re doing it. The idea of being able to step by step migrate this application to a SaaS-ish version is kind of cool — it’s better than the alternative, where they just say you have to start over. And GraphQL is the future for a lot of this stuff; it’s probably the way a lot of that should have worked in the first place. […] But if we’d just had this from the beginning, I think it would have been a lot less painful. So, I think we can get there eventually.” — Clay
The friction came in two forms. Some of it was timing and clarity: supporting features and packages arrived after the platform that depends on them — Commerce seven weeks behind CMS, Service API months behind that — and documentation moved or changed while work was already in flight. The rest is structural and permanent. Migrating from Find to Graph proved to be a far larger endeavor than anticipated, and because Find has no CMS 13 version at all, that cost isn't optional.
It's too early to thoroughly report the upside since neither site is live. Nearly all the effort so far has gone into restoring functionality these customers already had, which is why a conversation about this upgrade today is necessarily a conversation about pain points. The capabilities CMS 13 is being bought for — visual authoring, Graph-backed features, the incremental path toward SaaS — haven't reached editors or shoppers yet. That half of the verdict is genuinely unwritten, and worth revisiting once the websites have been live for some time.
“Right now we’re still trying to finish actually getting things developed — some things haven’t even started to be tested yet. So we talk a lot about these pain points, but that’s just because that’s what we’ve been on so far. I’d like to think some of these more positive impacts will become visible once we’re actually using them.” — Caleb
If you're budgeting a CMS 13 upgrade, make sure to account for the search migration, the supporting packages, and possibly rebuilding customizations — that's where most of the effort goes.


