# Bc Upgrade Codeunit Generator

> Generates upgrade codeunits (Subtype = Upgrade) for Business Central extensions that run data migration code when upgrading to a new version. Creates codeunits with OnUpgradePerCompany and OnUpgradePerDatabase triggers, precondition checks via OnCheckPreconditionsPerCompany and OnCheckPreconditionsPerDatabase, post-upgrade validation via OnValidateUpgradePerCompany and OnValidateUpgradePerDatabas…

- **Type:** Skill
- **Install:** `agentstack add skill-fernandoartalf-al-copilot-skills-collection-bc-upgrade-codeunit-generator`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [fernandoartalf](https://agentstack.voostack.com/s/fernandoartalf)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [fernandoartalf](https://github.com/fernandoartalf)
- **Source:** https://github.com/fernandoartalf/AL-Copilot-Skills-Collection/tree/main/skills/bc-upgrade-codeunit-generator
- **Website:** https://alcopilotskills.com/

## Install

```sh
agentstack add skill-fernandoartalf-al-copilot-skills-collection-bc-upgrade-codeunit-generator
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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:

```al
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

```al
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

```al
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

```al
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

```al
trigger OnUpgradePerDatabase()
begin
    NavApp.RestoreArchiveData(Database::"[Prefix] Custom Table");
end;
```

### Pattern 6: Precondition — Block Incompatible Versions

```al
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 `SetLoadFields`** before `FindSet` when 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

1. **Identify data changes** — What changed between versions? New fields, removed fields, table restructuring?
2. **Choose control method** — Version comparison or upgrade tags?
3. **Plan migration steps** — List each data transformation needed, in dependency order
4. **Create upgrade tag definitions** — One tag per migration step, registered for new companies
5. **Implement upgrade codeunit** — Migration logic guarded by tags with `SetLoadFields` and `Modify(false)`
6. **Add precondition checks** — Block incompatible source versions
7. **Add validation checks** — Verify each migration step succeeded
8. **Update install codeunit** — Set upgrade tags on fresh install (`SetAllUpgradeTags` or individual tags)
9. **Add telemetry** — Log start/end of each step with record counts
10. **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.al`
- `BCSStatAccUpgrade.Codeunit.al` / `BCSStatAccUpgradeTagDef.Codeunit.al`

Place in: `src/Codeunit/` or feature folder `src/[Feature]/Codeunit/`.

## Checklist

Before completing upgrade codeunit generation:

- [ ] `Subtype = Upgrade` set 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
- [ ] `SetLoadFields` used before `FindSet` on 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](https://learn.microsoft.com/dynamics365/business-central/dev-itpro/developer/devenv-upgrading-extensions) — Microsoft Docs
- [Writing Extension Install Code](https://learn.microsoft.com/dynamics365/business-central/dev-itpro/developer/devenv-extension-install-code) — Install codeunits (companion)
- [NavApp Data Type](https://learn.microsoft.com/dynamics365/business-central/dev-itpro/developer/methods-auto/navapp/navapp-data-type) — ModuleInfo methods
- [Upgrade Tags (BCApps)](https://github.com/microsoft/BCApps/tree/main/src/System%20Application/App/Upgrade%20Tags) — System Application source code
- [Analyzing Extension Upgrade Telemetry](https://learn.microsoft.com/dynamics365/business-central/dev-itpro/administration/telemetry-extension-update-trace) — 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](https://github.com/fernandoartalf)
- **Source:** [fernandoartalf/AL-Copilot-Skills-Collection](https://github.com/fernandoartalf/AL-Copilot-Skills-Collection)
- **License:** MIT
- **Homepage:** https://alcopilotskills.com/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-fernandoartalf-al-copilot-skills-collection-bc-upgrade-codeunit-generator
- Seller: https://agentstack.voostack.com/s/fernandoartalf
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
