Email this post as a PDFDevelopment · 9 October 2026 · 3 min read

Multi-company code in AL: company initialisation, ChangeCompany and per-database data

A tenant can have many companies, and an extension installs into all of them at once. Code that assumes one company breaks on the second. Install and upgrade per company versus per database, OnCompanyInitialize, reading another company's data with ChangeCompany, tables that are DataPerCompany = false, and the job queue trap.

Contents
  1. Install and upgrade: per company or per database
  2. New companies
  3. Reading another company
  4. Tables that are not per company
  5. The job queue trap
  6. Testing

Everything in Business Central is per company by default: every table, every setup record, every number series. A tenant with five companies has five Item tables. An extension that was written and tested in Cronus and then installed into a group with twelve companies meets this on day one.

Install and upgrade: per company or per database

Install codeunits have [OnInstallAppPerCompany] (runs once in each company, now and for each company created later) and [OnInstallAppPerDatabase] (once per tenant). Upgrade likewise. Setup records, default data, retention policy registration: per company. Registering a feature key or anything in a DataPerCompany = false table: per database. Getting this wrong means a new company has no setup record (per-database install never ran for it) or a per-database table gets twelve identical rows.

New companies

When a company is created (admin centre or Companies page), the platform runs the per-company install for every installed app, and raises OnCompanyInitialize in Codeunit "Company-Initialize". Subscribe to it for anything that must exist in a fresh company (your setup record, your number series defaults, your report selections). Do not assume a company was created before your app was installed.

Reading another company

procedure GroupStockForItem(ItemNo: Code[20]) Total: Decimal
var
    Company: Record Company;
    ILE: Record "Item Ledger Entry";
begin
    if Company.FindSet() then
        repeat
            ILE.ChangeCompany(Company.Name);
            ILE.SetRange("Item No.", ItemNo);
            ILE.CalcSums(Quantity);
            Total += ILE.Quantity;
        until Company.Next() = 0;
end;

ChangeCompany points a record variable at another company's table; reads and writes go there. Rules: the user needs permission in that company; triggers do not run on writes in the other company (so Insert(true) is Insert()); FlowFields work, CalcSums works; and it is not a transaction boundary, so a failure rolls back both. Use it for group reporting and intercompany, sparingly, and never from a posting routine.

Tables that are not per company

DataPerCompany = false on a table makes one copy for the tenant: a shared log, a shared configuration, a cross-company queue. Use it when the data genuinely belongs to the tenant (an integration's token cache, a group-wide code list). Permissions still apply; retention still applies. The Company record itself, users and permission sets are per database.

The job queue trap

A job queue entry belongs to the company it was created in and runs there. "The integration runs in the UK company but not the Irish one" is a job queue entry that exists in one company. Either create one per company (from the per-company install, if the job should run everywhere) or write one job that loops the companies with ChangeCompany and runs in a designated company. Document which.

Testing

Add a second company to the pipeline's container and run the install twice, and have at least one test that creates a company (insert into the Company table, then run Codeunit "Company-Initialize") and checks that your setup record and defaults exist in it. The second company is where multi-company bugs live, and the demo database only has one.

Email this post as a PDF
A branded PDF of this post, sent to your inbox to keep, print or share. Copy in colleagues if you like.
We only use these addresses to send this PDF. Nobody is added to a mailing list. Mike at Business Boxed receives a copy. See our privacy notice.
Email this post
Talk to us
Want this done in your Business Central?
Book a 30 minute callGet the monthly newsletter

Related posts