Per Nergård (MVP)
Jun 12, 2026
  93
(0 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

Please login to comment.
Latest blogs
Designing ODP Real-Time Audiences for CMS Personalization and Experimentation

A practical look at when to use ODP Real-Time Audiences, how to build them, and how they fit into CMS personalization and Feature Experimentation.

Wojciech Seweryn | Jun 11, 2026 |

Unlock Experimentation with Content Variations in CMS 13

Part 1 argued that Content Variations is the CMS 13 feature that didn't get the keynote but should have. This is the follow-up: wiring those...

Piotr | Jun 11, 2026

umage.ai is now an Optimizely Silver Solution Partner

umage.ai is officially an Optimizely Silver Solution Partner. The badge formalises an alignment that was already there — agent-driven Optimizely wo...

Allan Thraen | Jun 10, 2026 |