Skill: AL Project Migration
Purpose
Plan and execute BC platform version upgrades: app.json configuration, breaking-change remediation, deprecated API replacement, event signature updates, upgrade codeunits for data migration, rollback strategy, and manifest regeneration.
When to Load
This skill should be loaded when:
- Upgrading an AL extension to a newer BC platform version (e.g., BC 23.x → 24.x)
- Fixing compilation errors after updating the runtime or platform property
- Replacing deprecated AL patterns (C/AL legacy, obsolete APIs)
- Updating event subscriber signatures after base-app changes
- Writing upgrade codeunits for schema changes or data migration
- Generating a full deployment package after migration
Core Patterns
For BC29 / AL18, first read AL18 capability checks when selecting new manifest, public-resource, isolation, testing or translation features. Apply only the sections needed by the requirement; distinguish announced capabilities from locally verified declarations. Keep BC28 configuration unchanged unless an upgrade is part of the approved task.
Pattern 1: App.json Platform Update
Update the three version-sensitive properties in app.json:
json{ "platform": "25.0.0.0", "runtime": "14.0", "application": "25.0.0.0", "dependencies": [ { "id": "63ca2fa4-4f03-4f2b-a480-172fef340d3f", "name": "System Application", "publisher": "Microsoft", "version": "25.0.0.0" } ], "features": ["TranslationFile", "GenerateCaptions", "NoImplicitWith"] }
Rules:
platform— target BC platform version (major.minor.0.0)runtime— AL runtime version matching the target (see runtime matrix)application— must match or be compatible with target platform- Resolve each dependency by identity, minimum version, target runtime and required APIs; do not require every library version to numerically match the application version.
- Add new
featuresflags required by the target runtime (e.g.,NoImplicitWithfrom runtime 11.0+)
Pattern 2: Deprecated Code Replacement
Replace legacy patterns with modern equivalents:
al// ❌ Deprecated — C/AL style Record.FIND('-'); Record.FINDSET(TRUE, TRUE); IF Record.FINDFIRST THEN; Record.INIT; Record.INSERT; // ✅ Modern — AL patterns Record.FindSet(); Record.FindSet(true, true); if Record.FindFirst() then; Record.Init(); Record.Insert(true);
al// ❌ Deprecated — WITH statement (removed in NoImplicitWith) with SalesHeader do begin "Document Type" := "Document Type"::Order; "Sell-to Customer No." := CustomerNo; Insert(true); end; // ✅ Modern — explicit record reference SalesHeader."Document Type" := SalesHeader."Document Type"::Order; SalesHeader."Sell-to Customer No." := CustomerNo; SalesHeader.Insert(true);
al// ❌ Deprecated — TextConst (runtime < 6.0) CustomerNotFoundErr@1000 : TextConst 'ENU=Customer %1 not found.'; // ✅ Modern — Label var CustomerNotFoundErr: Label 'Customer %1 not found.';
Pattern 3: Event Signature Migration
When base-app events add or change parameters between versions:
al// BC 23.x subscriber — 3 parameters [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", OnBeforePostSalesDoc, '', false, false)] local procedure OnBeforePost( var SalesHeader: Record "Sales Header"; CommitIsSuppressed: Boolean; var IsHandled: Boolean) begin // ... end; // BC 24.x — same event now has 4 parameters (new PreviewMode added) [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", OnBeforePostSalesDoc, '', false, false)] local procedure OnBeforePost( var SalesHeader: Record "Sales Header"; CommitIsSuppressed: Boolean; PreviewMode: Boolean; var IsHandled: Boolean) begin // ... end;
Migration steps:
- Build with
al_build— signature mismatches produceAL0482errors - Use
al_get_object_definitionto inspect the new publisher signature - Update parameter list to match exactly (name, type, order)
- Re-verify with
al_build
Pattern 4: Obsolete Object Handling
Handle objects marked ObsoleteState = Removed in the target version:
al// Step 1: Find usage of removed objects // al_search_objects — search for the obsolete table/page/codeunit // Step 2: Replace with the designated successor // Before (removed in BC 24): // Codeunit 80 "Sales-Post (Yes/No)" — ObsoleteState = Removed // After: Codeunit.Run(Codeunit::"Sales-Post", SalesHeader); // Step 3: Check ObsoleteReason for migration guidance // ObsoleteReason typically says: "Use codeunit X instead"
Process:
- Compile → collect all
AL0503(removed) andAL0432(pending) warnings - For
Removed— must fix before compilation succeeds - For
Pending— fix proactively to avoid future breaks - Check release notes for each removed object's replacement
Pattern 5: Upgrade Codeunit (Data Migration)
Use upgrade codeunits to transform data when schema changes between versions:
alcodeunit 50100 "Contoso Data Upgrade" { Subtype = Upgrade; trigger OnUpgradePerCompany() var Module: Info; begin Module.DataVersion(1); // check data version tracking MigrateCustomerLoyaltyData(); SplitAddressFields(); end; trigger OnValidateUpgradePerCompany() begin // Pre-upgrade validation — runs before OnUpgradePerCompany VerifyDataIntegrity(); end; local procedure MigrateCustomerLoyaltyData() var Customer: Record Customer; ContosoLoyalty: Record "Contoso Loyalty Entry"; UpgradeTag: Codeunit "Upgrade Tag"; begin if UpgradeTag.HasUpgradeTag(GetLoyaltyMigrationTag()) then exit; // Migrate data from old field to new table Customer.SetFilter("Contoso Legacy Points", '>0'); if Customer.FindSet() then repeat ContosoLoyalty.Init(); ContosoLoyalty."Customer No." := Customer."No."; ContosoLoyalty.Points := Customer."Contoso Legacy Points"; ContosoLoyalty."Entry Date" := WorkDate(); ContosoLoyalty.Insert(); until Customer.Next() = 0; UpgradeTag.SetUpgradeTag(GetLoyaltyMigrationTag()); end; local procedure GetLoyaltyMigrationTag(): Code[250] begin exit('CONTOSO-LOYALTY-MIGRATION-20260301'); end; }
Upgrade codeunit rules:
Subtype = Upgrade— BC runs these automatically during app upgrade- Use
UpgradeTagto ensure idempotency — never run migration twice OnValidateUpgradePerCompanyruns first — validate data before transformingOnUpgradePerCompanyruns per company — do the actual migration- Always test with a copy of production data before deploying
- For large datasets, use
SelectLatestVersion()and batch processing
Pattern 6: Rollback Strategy
Document and prepare rollback before executing migration:
markdown## Rollback Plan — {Project} Migration v{X} → v{Y} ### Pre-Migration Checklist - [ ] Git branch created from stable tag: `git checkout -b migration/vX-to-vY vX.0.0` - [ ] Database backup taken and verified - [ ] Extension .app file of current version archived - [ ] Rollback tested in sandbox environment ### Rollback Procedure 1. **Code rollback**: `git checkout vX.0.0` — restore previous version 2. **Extension rollback**: Uninstall new version, install archived .app 3. **Data rollback** (if upgrade codeunit ran): - Restore database from pre-migration backup - OR run compensating downgrade codeunit (if written) 4. **Verify**: Run smoke tests on restored environment ### Point of No Return ⚠️ After these actions, rollback requires database restore: - Upgrade codeunits that DELETE data - Schema changes that DROP columns - External system notifications sent
Rollback rules:
- Always create a rollback plan BEFORE starting migration
- Tag the pre-migration commit:
git tag vX.0.0-pre-migration - Archive the current .app file alongside the plan
- Test rollback in sandbox — never assume it works
- If upgrade codeunit is destructive (deletes data), document the point of no return
Workflow
Step 1: Pre-Migration Assessment
- Backup: Ensure source control is up to date (
git statusclean) - Download current symbols:
al_downloadsymbols - Document dependencies:
al_packages— list loaded packages with current versions - Review release notes: Check BC target version breaking changes
- Create migration plan in
.claude/plans/{project}-migration.md
PAUSE — wait for user approval before modifying files.
Step 2: Update Configuration
- Update
app.json(Pattern 1) — platform, runtime, application, dependencies, features - Download new symbols for target version
- Build:
al_build— collect all errors
Step 3: Fix Compilation Errors
Prioritize by error type:
- AL0503 (Removed objects) → Pattern 4
- AL0482 (Event signature mismatch) → Pattern 3
- AL0432 (Deprecated usage) → Pattern 2
- Other errors → case-by-case analysis
For each fix, verify with incremental build.
Step 4: Regenerate and Validate
- Update the manifest (
app.json) — no agent tool; edit directly (or via the VS Code command) - Full build:
al_build— zero errors, zero new warnings;al_buildalso produces the.apppackage - Run existing tests to verify no regressions
Step 5: Post-Migration
- Update CHANGELOG with migration notes
- Tag the commit with new version
- Test in sandbox environment before production
Version-Specific Notes
| Upgrade Path | Key Breaking Changes |
|---|---|
| BC 20 → 21 | New permission model, page layout changes |
| BC 21 → 22 | Namespace support, NoImplicitWith enforcement |
| BC 22 → 23 | Async patterns, isolated events, security hardening |
| BC 23 → 24 | New AL capabilities, event parameter additions |
| BC 24 → 25 | Enhanced debugging, agent integration, new runtime features |
References
- Choosing the Runtime Version
- Breaking Changes per Release
- ObsoleteState Property
- App.json Properties
- NoImplicitWith Feature
- Upgrade Codeunits
- Upgrade Tags
Constraints
- This skill covers version migration planning, breaking-change remediation, and configuration updates
- Do NOT modify base BC objects — extension-only changes
- Do NOT skip the pre-migration backup and assessment step
- Do NOT combine migration with feature development — migrate first, then develop
- Always create a migration plan and obtain approval before modifying files
- Event debugging →
skill-debug.md| Performance after migration →skill-performance.md| Test verification →skill-testing.md


