Install
$ agentstack add skill-fernandoartalf-al-copilot-skills-collection-bc-upgrade-codeunit-generator ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Business Central Upgrade Codeunit Generator
Generates production-ready upgrade codeunits for Business Central extensions. Handles data migration between versions, precondition validation, upgrade tag management, and post-upgrade verification.
Overview
Upgrade codeunits (Subtype = Upgrade) run automatically when an extension is upgraded to a newer version (higher version number in app.json). They do not run during fresh installs or reinstalls — use install codeunits for that.
Upgrade code runs when:
- A new version of an already-installed extension is published and data-upgraded
- The new version has a higher version number than the currently installed version
This skill generates:
- Upgrade codeunit with all six triggers
- Precondition checks to block incompatible upgrades
- Data migration procedures with upgrade tag guards
- Post-upgrade validation
- Companion upgrade tag definitions codeunit
- Install codeunit integration (setting tags on fresh install)
- ExecutionContext guards for sensitive event subscribers
Complete examples and patterns: [references/upgrade-examples.md](references/upgrade-examples.md)
Prerequisites
- AL workspace with established object ID range
- Prefix established (e.g., BCS, ABC, etc.)
- Knowledge of what data needs migration between versions
- System Application dependency (for Upgrade Tag module)
- Existing install codeunit (to register upgrade tags on fresh install)
Quick Start — Version-Based Control
For simple extensions with few versions:
codeunit [ID] "[Prefix] Upgrade"
{
Subtype = Upgrade;
trigger OnCheckPreconditionsPerCompany()
begin
// Validate upgrade can proceed
end;
trigger OnUpgradePerCompany()
var
AppInfo: ModuleInfo;
begin
NavApp.GetCurrentModuleInfo(AppInfo);
// DataVersion = version upgrading FROM
if AppInfo.DataVersion 1x/year) | | **Recommended** |
| Version is set manually | **Recommended** | |
| Checking for first-time install (`0.0.0.0`) | **Recommended** | |
| Fixing a broken upgrade | | **Recommended** |
## Upgrade Tag Convention
Format: `[Prefix]-[ObjectID]-[Description]-[YYYYMMDD]`
Examples:
- `BCS-50100-ShoeSizeUpgrade-20260101`
- `BCS-50100-FieldMigration-20260315`
- `ABC-50200-EnumConversion-20260401`
## Three-Codeunit Pattern (Upgrade Tags)
When using upgrade tags, implement three companion codeunits:
### 1. Upgrade Codeunit (`Subtype = Upgrade`)
Contains the actual upgrade logic, guarded by `HasUpgradeTag` / `SetUpgradeTag`.
### 2. Upgrade Tag Definitions Codeunit
- Defines tag values as procedures (not hard-coded strings)
- Subscribes to `OnGetPerCompanyUpgradeTags` / `OnGetPerDatabaseUpgradeTags` to register tags for new companies
- `Access = Internal` to prevent external dependencies on tag values
### 3. Install Codeunit (`Subtype = Install`)
- Calls `UpgradeTag.SetAllUpgradeTags()` (or sets individual tags) on `OnInstallAppPerCompany`
- Ensures upgrade code doesn't run on freshly installed extensions
## Common Upgrade Patterns
### Pattern 1: Field Migration (Old Field → New Field)
```al
local procedure MigrateFieldData()
var
UpgradeTagDef: Codeunit "[Prefix] Upgrade Tag Def.";
UpgradeTag: Codeunit "Upgrade Tag";
Customer: Record Customer;
begin
if UpgradeTag.HasUpgradeTag(UpgradeTagDef.GetFieldMigrationTag()) then
exit;
Customer.SetLoadFields("[Prefix] Old Field", "[Prefix] New Field");
Customer.SetFilter("[Prefix] Old Field", '<>%1', '');
Customer.SetRange("[Prefix] New Field", '');
if Customer.FindSet() then
repeat
Customer."[Prefix] New Field" := Customer."[Prefix] Old Field";
Customer.Modify(false);
until Customer.Next() = 0;
UpgradeTag.SetUpgradeTag(UpgradeTagDef.GetFieldMigrationTag());
end;
Pattern 2: Populate New Table from Existing Data
local procedure PopulateNewTable()
var
UpgradeTagDef: Codeunit "[Prefix] Upgrade Tag Def.";
UpgradeTag: Codeunit "Upgrade Tag";
Source: Record "Source Table";
Target: Record "[Prefix] New Table";
begin
if UpgradeTag.HasUpgradeTag(UpgradeTagDef.GetNewTableTag()) then
exit;
Source.SetLoadFields("No.", "Field A", "Field B");
if Source.FindSet() then
repeat
if not Target.Get(Source."No.") then begin
Target.Init();
Target."Entry No." := Source."No.";
Target."Field A" := Source."Field A";
Target."Field B" := Source."Field B";
Target.Insert(false);
end;
until Source.Next() = 0;
UpgradeTag.SetUpgradeTag(UpgradeTagDef.GetNewTableTag());
end;
Pattern 3: Enum Value Migration
local procedure MigrateEnumValues()
var
UpgradeTagDef: Codeunit "[Prefix] Upgrade Tag Def.";
UpgradeTag: Codeunit "Upgrade Tag";
MyRecord: Record "[Prefix] My Record";
begin
if UpgradeTag.HasUpgradeTag(UpgradeTagDef.GetEnumMigrationTag()) then
exit;
MyRecord.SetLoadFields(Status, "Legacy Status Code");
MyRecord.SetRange(Status, MyRecord.Status::" ");
MyRecord.SetFilter("Legacy Status Code", '<>%1', 0);
if MyRecord.FindSet() then
repeat
case MyRecord."Legacy Status Code" of
1:
MyRecord.Status := MyRecord.Status::Draft;
2:
MyRecord.Status := MyRecord.Status::Active;
3:
MyRecord.Status := MyRecord.Status::Closed;
end;
MyRecord.Modify(false);
until MyRecord.Next() = 0;
UpgradeTag.SetUpgradeTag(UpgradeTagDef.GetEnumMigrationTag());
end;
Pattern 4: Default Value Seeding on Upgrade
local procedure SetNewFieldDefaults()
var
UpgradeTagDef: Codeunit "[Prefix] Upgrade Tag Def.";
UpgradeTag: Codeunit "Upgrade Tag";
Setup: Record "[Prefix] Module Setup";
begin
if UpgradeTag.HasUpgradeTag(UpgradeTagDef.GetDefaultValuesTag()) then
exit;
if Setup.Get() then begin
Setup."New Feature Enabled" := false;
Setup."Max Items" := 100;
Setup.Modify(false);
end;
UpgradeTag.SetUpgradeTag(UpgradeTagDef.GetDefaultValuesTag());
end;
Pattern 5: Data Archive Restoration
trigger OnUpgradePerDatabase()
begin
NavApp.RestoreArchiveData(Database::"[Prefix] Custom Table");
end;
Pattern 6: Precondition — Block Incompatible Versions
trigger OnCheckPreconditionsPerCompany()
var
AppInfo: ModuleInfo;
begin
NavApp.GetCurrentModuleInfo(AppInfo);
if AppInfo.DataVersion ExecutionContext::Normal then
exit;
// Safe to call external services here
end;
ExecutionContext Values
| Value | When | |-------|------| | ExecutionContext::Normal | Normal user/API operation | | ExecutionContext::Install | During extension install | | ExecutionContext::Uninstall | During extension uninstall | | ExecutionContext::Upgrade | During extension upgrade |
Design Guidelines
- Use upgrade tags for anything beyond trivial upgrades — they're more robust than version checks
- Register tags for new companies via
OnGetPerCompanyUpgradeTags/OnGetPerDatabaseUpgradeTags - Set tags on fresh install via install codeunit so upgrade code doesn't run on new data
- Use
Modify(false)during bulk migration to skip trigger execution - Use
SetLoadFieldsbeforeFindSetwhen iterating large tables - Keep safety checks — verify target fields are blank/default before overwriting
- No UI interaction — upgrade code runs without user context
- Idempotent — upgrade may be retried after failure; guard with tags
- Independent codeunits — don't depend on execution order between upgrade codeunits
- Validate after upgrade — use validation triggers to confirm migration succeeded
- Log telemetry — record step names, record counts, and timing in Application Insights
- Limit tag nesting — keep tag checks to max two levels of nesting
Upgrade Codeunit Design Workflow
- Identify data changes — What changed between versions? New fields, removed fields, table restructuring?
- Choose control method — Version comparison or upgrade tags?
- Plan migration steps — List each data transformation needed, in dependency order
- Create upgrade tag definitions — One tag per migration step, registered for new companies
- Implement upgrade codeunit — Migration logic guarded by tags with
SetLoadFieldsandModify(false) - Add precondition checks — Block incompatible source versions
- Add validation checks — Verify each migration step succeeded
- Update install codeunit — Set upgrade tags on fresh install (
SetAllUpgradeTagsor individual tags) - Add telemetry — Log start/end of each step with record counts
- Test upgrade path — Verify from each supported source version to the new version
File Naming Convention
Follow the pattern:
- Upgrade codeunit:
[Prefix]Upgrade.Codeunit.al - Upgrade tag definitions:
[Prefix]UpgradeTagDef.Codeunit.al
Examples:
BCSUpgrade.Codeunit.al/BCSUpgradeTagDef.Codeunit.alBCSStatAccUpgrade.Codeunit.al/BCSStatAccUpgradeTagDef.Codeunit.al
Place in: src/Codeunit/ or feature folder src/[Feature]/Codeunit/.
Checklist
Before completing upgrade codeunit generation:
- [ ]
Subtype = Upgradeset on upgrade codeunit - [ ] Upgrade triggers used appropriately (Preconditions → Upgrade → Validate)
- [ ] Upgrade tags created for each migration step (if using tag pattern)
- [ ] Tag definitions codeunit created with
Access = Internal - [ ] Tags registered for new companies via
OnGetPerCompanyUpgradeTags/OnGetPerDatabaseUpgradeTags - [ ] Install codeunit updated to set tags on fresh install
- [ ]
SetLoadFieldsused beforeFindSeton large tables - [ ]
Modify(false)used for bulk data migration (skip triggers) - [ ] Safety checks before overwriting fields (verify blank/default)
- [ ] No UI interaction (no Message, Confirm, Page.Run)
- [ ] Precondition checks block incompatible source versions
- [ ] Validation checks verify migration success
- [ ] Telemetry logging for each step (optional)
- [ ] ExecutionContext guards on sensitive event subscribers
- [ ] File follows naming convention
References
For complete examples:
- [references/upgrade-examples.md](references/upgrade-examples.md) — Full working examples (version-based, upgrade tags, database-level, multi-step with telemetry, ExecutionContext guards, broken upgrade fixes, archive restoration, anti-patterns)
External Resources
- Upgrading Extensions — Microsoft Docs
- Writing Extension Install Code — Install codeunits (companion)
- NavApp Data Type — ModuleInfo methods
- Upgrade Tags (BCApps) — System Application source code
- Analyzing Extension Upgrade Telemetry — Upgrade telemetry signals
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: fernandoartalf
- Source: fernandoartalf/AL-Copilot-Skills-Collection
- License: MIT
- Homepage: https://alcopilotskills.com/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.