← All postsDevelopment ยท 3 November 2026

Changing a live extension without losing data: upgrade codeunits, obsolete tags and schema sync

The extension is in production, the data is in it, and the customer wants a field renamed, a table restructured and a feature retired. How Business Central decides whether your schema change is allowed, how to stage a breaking change over two releases, and how to write the upgrade codeunit that moves the data.

Building an extension is the easy part. Changing it once it has data in production is where developers who learned on a sandbox get into trouble, because Business Central will refuse a schema change that would lose data, and if you force it, you lose the data. This is how to make changes to a live extension safely, every time.

What schema sync will and will not allow

When a new version of an app is installed, the platform compares the new schema with the old and synchronises the database. Additive changes are always fine: new tables, new fields, new keys, widening a text field. Destructive changes are refused by default: removing a table or field, shrinking a field, changing a field's type, changing a primary key. The Add sync mode (the default and the only one on production online) will not do them. ForceSync, which will, is available on sandboxes and on-premise only, and it deletes the data for the changed objects. It is a development convenience, never a deployment strategy.

So every destructive change in production is done by staging, not by force.

Staging a breaking change over releases

Suppose a field "Pallet Config" (Code[10]) needs to become "Pallet Configuration" (Code[30]) on a different table. The sequence:

  1. Release N: add the new. New field (or table) added. The old field marked ObsoleteState = Pending, ObsoleteReason and ObsoleteTag = '29.0'. An upgrade codeunit copies the data from old to new (below). Code now reads and writes the new field; the old field is still there, still has its data, and still compiles for anyone who references it, with a warning.
  2. Release N+1: remove the use. Old field to ObsoleteState = Removed. It no longer compiles for anyone; its column stays in the database with its data, inaccessible from AL. Any dependent app that still referenced it has had a release to move.
  3. Later: clean up. For removed fields, the column is dropped by the platform on a subsequent sync when all apps that know about it are gone. You do not have to do anything; you just do not get the space back immediately.

Renames of tables, primary key changes and type changes all follow the same shape: create the new, migrate, obsolete the old. Never reuse an object ID or a field ID for something different; the platform remembers.

The upgrade codeunit

A codeunit with Subtype = Upgrade runs when the app is upgraded from a lower version. Its procedures carry [OnUpgradePerCompany] (per company, most data moves) or [OnUpgradePerDatabase]. The shape that works:

Test it by installing version N-1 in a sandbox with realistic data, then upgrading to N, then checking the counts and spot-checking values. Then do it again from a fresh install to make sure the install path works. The same codeunit is what you write when consolidating several extensions into one: the new app depends on the old, the upgrade copies across, the old is obsoleted.

Dependencies make it harder

If another app depends on yours, your obsoleted field is a warning in their build at Pending and an error at Removed. Give them a version. If you depend on an ISV app and they obsolete something, your NextMajor build is where you find out; that is why the pipeline compiles weekly against the latest versions of dependencies, not just the base.

Uninstalling and data

Uninstalling an app does not delete its data; the data is kept, orphaned, until the app is reinstalled (when it reconnects) or someone chooses to delete it (Unpublish with data deletion, or the "delete data" option). In version 29, with extension fields stored on the base table, uninstalling an app with large tables is a heavier operation and should be done in a window. The rule for customers: never choose "delete data" for an app you might want back, and never uninstall an app to tidy up before checking whether anything is reading its fields.

The checklist

  1. Additive changes only in a single release; destructive changes staged over two.
  2. Obsolete with a reason and a tag; never delete; never reuse IDs.
  3. Upgrade codeunit with upgrade tags, install codeunit registering them, data copied not moved, telemetry on both ends.
  4. Tested from N-1 to N on a copy of production and from a fresh install.
  5. Dependants given a release to move.
  6. ForceSync in sandboxes only, and only when you mean it.

This is routine work once it is habit and a disaster the first time it is not. If you have a schema change you are nervous about, or an extension that will not install because of one, we have done enough of them to make it boring.

Talk to us
Want this done in your Business Central?
Book a 30 minute callGet the monthly newsletter

More from the blog