Skip to main content

Business Central Install Codeunit Generator

Generates production-ready install codeunits for Business Central extensions. Handles first install detection, data initialization, reinstall scenarios, and proper separation of per-company vs per-database operations.

Overview​

Install codeunits (Subtype = Install) run automatically when an extension is installed or reinstalled. They do not run during upgrades — use upgrade codeunits for that. Install code runs when:

  • An extension is installed for the first time
  • An uninstalled extension version is reinstalled

This skill generates:

  • Install codeunit with proper triggers
  • Fresh install vs reinstall detection
  • Data seeding procedures (setup records, defaults)
  • Per-company and per-database operation separation
  • Optional telemetry logging for install events

Complete examples and patterns: references/install-examples.md

Prerequisites​

  • AL workspace with established object ID range
  • Prefix established (e.g., BCS, CUSTOM, etc.)
  • Knowledge of what data/setup needs initialization
  • Setup table(s) identified (if seeding defaults)

Quick Start​

codeunit [ID] "[Prefix] Install"
{
Subtype = Install;

trigger OnInstallAppPerDatabase()
var
AppInfo: ModuleInfo;
begin
NavApp.GetCurrentModuleInfo(AppInfo);

if AppInfo.DataVersion = Version.Create(0, 0, 0, 0) then
HandleFreshInstallDatabase()
else
HandleReinstallDatabase();
end;

trigger OnInstallAppPerCompany()
var
AppInfo: ModuleInfo;
begin
NavApp.GetCurrentModuleInfo(AppInfo);

if AppInfo.DataVersion = Version.Create(0, 0, 0, 0) then
HandleFreshInstallCompany()
else
HandleReinstallCompany();
end;

local procedure HandleFreshInstallDatabase()
begin
// Database-wide operations for first install
end;

local procedure HandleReinstallDatabase()
begin
// Database-wide operations for reinstall
end;

local procedure HandleFreshInstallCompany()
begin
// Per-company operations for first install
end;

local procedure HandleReinstallCompany()
begin
// Per-company operations for reinstall
end;
}

Install Triggers​

TriggerScopeRunsUse For
OnInstallAppPerDatabase()Once per installOne time regardless of company countDatabase-level setup: permission assignments, app-wide configuration, telemetry registration
OnInstallAppPerCompany()Per companyOnce for each company in the databaseCompany-specific data: setup records, default values, number series, demo data

Key rule: Per-database triggers run first, per-company triggers run once for each company.

Fresh Install vs Reinstall Detection​

Use ModuleInfo.DataVersion to distinguish between first install and reinstall:

var
AppInfo: ModuleInfo;
begin
NavApp.GetCurrentModuleInfo(AppInfo);

if AppInfo.DataVersion = Version.Create(0, 0, 0, 0) then
// First-time install — DataVersion is 0.0.0.0
else
// Reinstall — same version being installed again
end;

ModuleInfo Properties​

PropertyTypeDescription
DataVersionVersionData version of the extension. 0.0.0.0 on first install.
AppVersionVersionApp version from app.json
IdGuidExtension App ID
NameTextExtension name
PublisherTextExtension publisher
DependenciesList of ModuleDependencyInfoExtension dependencies

Common Install Patterns​

Pattern 1: Setup Record Initialization​

Create the singleton setup record if it doesn't exist:

local procedure InitializeSetupRecord()
var
MySetup: Record "[Prefix] [Module] Setup";
begin
if not MySetup.Get() then begin
MySetup.Init();
MySetup.Insert(true);
end;
end;

Pattern 2: Default Configuration Values​

Seed default values into the setup record:

local procedure SetDefaultConfiguration()
var
MySetup: Record "[Prefix] [Module] Setup";
begin
if not MySetup.Get() then begin
MySetup.Init();
MySetup."Enable Feature" := true;
MySetup."Default Language" := 'ENU';
MySetup."Max Retry Count" := 3;
MySetup.Insert(true);
end;
end;

Pattern 3: Seed Reference Data​

Populate reference/lookup tables with initial data:

local procedure SeedReferenceData()
var
Category: Record "[Prefix] Category";
begin
InsertCategoryIfNotExists(Category, 'GENERAL', 'General Category');
InsertCategoryIfNotExists(Category, 'SPECIAL', 'Special Category');
end;

local procedure InsertCategoryIfNotExists(var Category: Record "[Prefix] Category"; Code: Code[20]; Description: Text[100])
begin
if not Category.Get(Code) then begin
Category.Init();
Category.Code := Code;
Category.Description := Description;
Category.Insert(true);
end;
end;

Pattern 4: Reinstall Data Patch​

Check and repair data consistency on reinstall:

local procedure HandleReinstallCompany()
begin
PatchMissingDefaults();
ValidateExistingData();
end;

local procedure PatchMissingDefaults()
var
MySetup: Record "[Prefix] [Module] Setup";
begin
if MySetup.Get() then begin
if MySetup."Max Retry Count" = 0 then
MySetup."Max Retry Count" := 3;
MySetup.Modify(true);
end else
SetDefaultConfiguration();
end;

Pattern 5: Telemetry on Install​

Log install events for monitoring:

local procedure LogInstallTelemetry(FreshInstall: Boolean)
var
AppInfo: ModuleInfo;
Dimensions: Dictionary of [Text, Text];
begin
NavApp.GetCurrentModuleInfo(AppInfo);
Dimensions.Add('ExtensionName', AppInfo.Name);
Dimensions.Add('ExtensionVersion', Format(AppInfo.AppVersion));
Dimensions.Add('FreshInstall', Format(FreshInstall));

Session.LogMessage(
'BCS-0001',
'Extension installed',
Verbosity::Normal,
DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher,
Dimensions
);
end;

Design Guidelines​

  • Idempotent: Install code may run multiple times (reinstall). Always check before inserting records.
  • No user interaction: Install code runs without UI — no Confirm, Message, or page calls.
  • Error handling: Wrap risky operations in error handling. A failing install codeunit blocks the extension install.
  • Multiple codeunits: You can have more than one install codeunit per extension, but execution order is not guaranteed. Each must be independent.
  • Not for upgrades: Install code does NOT run when upgrading to a new version. Use upgrade codeunits (Subtype = Upgrade) for that.
  • Performance: Keep install code fast. Avoid long-running operations that could time out.

Install Codeunit Design Workflow​

  1. Identify initialization needs — What data/config must exist after install?
  2. Separate concerns — Database-wide vs per-company operations
  3. Plan fresh vs reinstall — Different logic for each scenario
  4. Implement idempotently — All inserts check existence first
  5. Add telemetry — Log install for monitoring (optional)
  6. Test both paths — Verify fresh install and reinstall scenarios

File Naming Convention​

Follow the pattern: [Prefix]Install.Codeunit.al

Examples:

  • BCSInstall.Codeunit.al
  • BCSStatAccInstall.Codeunit.al

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

Checklist​

Before completing install codeunit generation:

  • Subtype = Install set on codeunit
  • Fresh install vs reinstall detection using DataVersion
  • OnInstallAppPerCompany for company-specific data
  • OnInstallAppPerDatabase for database-wide operations (if needed)
  • All inserts are idempotent (check before insert)
  • No UI interaction (no Message, Confirm, Page.Run)
  • Setup records initialized with sensible defaults
  • Reference data seeded if applicable
  • Telemetry logging added (optional)
  • File follows naming convention: [Prefix]Install.Codeunit.al

References​

For complete examples:

External Resources​