Per Nergård (MVP)
OMVPSaaS CMS Developer CertificationCommerce Connect 13 Developer Certification +2
Jun 12, 2026
visibility 799
star star star star star
(2 votes)

Four database surprises when upgrading from CMS 11 to CMS 13

We're in the middle of migrating a fairly large site from CMS 11 / .NET Framework to CMS 13 / .NET 10. The code migration is one thing, but the database is where the real surprises live. Everything worked fine against our dev and stage databases — and then we pointed the upgrade at a copy of the production database.

Here are four things that bit us. Two of them are reported to Optimizely (one verified as a bug and fixed in the latest release, one in triage), so depending on when you read this you might be spared. The other two are by design in CMS 13 and you could hit them if your solution is old enough.
 
1. NULL values in tblSiteDefinition stop the site from booting
 
CMS 13 replaces `SiteDefinition` with the new `Application` model, and on first boot a schema migration moves your rows from `tblSiteDefinition` to `tblApplication` with a plain `INSERT ... SELECT`. The catch: `Saved` and `SavedBy` are `NOT NULL` in the new table, and in our production database some of the old rows had NULL in those columns. The migration rolls back with an INSERT NULL violation and the site doesn't start at all.

The kicker is that our stage databases didn't have this problem — only production did. So if your upgrade rehearsals all run against stage copies, you can sail through every test and still faceplant on go-live night.

The fix is a one-off backfill before the first CMS 13 boot:
 
UPDATE tblSiteDefinition
SET Saved   = ISNULL(Saved, GETUTCDATE()),
    SavedBy = ISNULL(SavedBy, 'system')
WHERE Saved IS NULL OR SavedBy IS NULL;
2. Tab names must be plain ASCII
 
CMS 13 validates tab definition names at model sync, and it's strict: no non-ASCII characters, no spaces, no hyphens. Being a Swedish project we had tab names like "Sökinställningar", "SEO-Settings" and "Externa länkar" — and the app refused to boot with:

```
ValidationException: The Name 'X' is not a valid format.
```

So all tab names need to be plain ASCII identifiers. They're internal keys though — if you want nicer editor-facing labels (or anything with åäö), that's handled with tab translations in your localization files.
 
3. "Content" and "Settings" are reserved tab names
 
This one is sneaky. CMS 13 reserves the tab names `Content` and `Settings` for its built-in tabs, and if you have those exact strings as your own tab names — which we did, in a perfectly innocent-looking GroupNames class — model sync throws a `ConflictingResourceException` against a freshly upgraded database.

The confusing part is that `SystemTabNames.Content` isn't the string "Content" at all — it's "Information". And `SystemTabNames.Settings` is "Advanced". So the constants never collide, only the literals do. The fix is simply to point your own constants at `SystemTabNames`:
 
public const string Content  = SystemTabNames.Content;   // "Information"
public const string Settings = SystemTabNames.Settings;  // "Advanced"
 
 
4. NULLs in tblSynchedUser break edit mode for everyone
 
The site authenticates with WS-Federation against an external ADFS, and not every identity comes with `email`, `givenname` or `surname` claims. After user sync those rows end up with NULL in the corresponding columns in `tblSynchedUser` — including one literally named `Metadata`. The columns are nullable, so we assumed that was fine.

It isn't. As soon as anyone opens edit mode, the CMS shell tries to subscribe the current user to feature notifications, reads through the user table, and dies:
 
System.Data.SqlTypes.SqlNullValueException: Data is Null.
   at EPiServer.DataAccess.Internal.SynchronizeUsersDB.<FindUsersAsync>g__CreateUser|18_0
   ...
   at FeatureNotificationService.SubscribeAsync
 
A single NULL row anywhere in the table is enough, and it doesn't have to belong to the user logging in. The public site keeps working — only edit mode breaks — which makes it genuinely confusing to troubleshoot.

The workaround is backfilling the nullable string columns with empty strings. The good news: we reported this one, Optimizely verified it as a bug, and a fix ships in the latest release. If you're on an earlier 13.x, the backfill is:
 
UPDATE tblSynchedUser SET Email            = '' WHERE Email IS NULL;
UPDATE tblSynchedUser SET GivenName        = '' WHERE GivenName IS NULL;
UPDATE tblSynchedUser SET LoweredGivenName = '' WHERE LoweredGivenName IS NULL;
UPDATE tblSynchedUser SET Surname          = '' WHERE Surname IS NULL;
UPDATE tblSynchedUser SET LoweredSurname   = '' WHERE LoweredSurname IS NULL;
UPDATE tblSynchedUser SET Metadata         = '' WHERE Metadata IS NULL;

 

Wrapping up
 
The common thread: none of this shows up on a clean database. It's the years of accumulated production data — old site definitions, legacy users synced before claims were complete, tab names from a more liberal era — that trip the upgrade. So rehearse against a fresh copy of *production*, not stage, before the real cutover.
 
Jun 12, 2026

Comments

Piotr
Piotr Jun 13, 2026 02:36 PM

I confirm, I went the same way.

error Please login to comment.
Latest blogs
Surviving a Dirty Feed. Part 3: Error Handling and Event Batching

Committing a 250,000-row Optimizely catalog import row by row, returning a per-row failure report, and coalescing catalog events into one per batch.

Stanisław Szołkowski | Oct 16, 2026 |

Building UI Extensions for Optimizely SaaS CMS

Introduction Developers have been extending the functionality of Optimizely PaaS CMS for years using addons built in C# and deployed as NuGet...

Mark Stott | Oct 15, 2026

Cheapskate AI Translator: Because Translation Should Not Require a Second Mortgage

There is a particular kind of software project that begins with a detailed architecture document, a product roadmap, and a carefully assembled...

Linh Doan Cuu | Oct 15, 2026

Computed Fields in CMS 13 Content Contracts with Optimizely Graph

Content Contracts in Optimizely CMS 13 allow different content types to share a common structure when querying Optimizely Graph. But what if the...

Mardy | Oct 14, 2026