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