Another entry in my long list of videos for AL beginners. This time it’s all about numbers series.

In this video, Erik walks through how to implement Number Series in AL for Business Central. He covers what Number Series are, how they work under the hood, the correct way to use them in your custom apps (hint: use the NoSeriesManagement codeunit), and several gotchas you should be aware of — including the “Allow Gaps” setting, delayed inserts, and unexpected UI pop-ups during transactions.
What Are Number Series?
Number Series is a concept that has been in Dynamics NAV/Business Central for ages. It provides automatic, sequential numbering for records — think customer numbers, invoice numbers, order numbers, and so on. Despite being a foundational feature, it can be surprisingly confusing to work with.
To find Number Series in Business Central, search for “Number Series” (note: the table name uses the abbreviation No. — as in No. Series — even though we’re talking about “numbers”). Within the Number Series construct, you have:
- Number Series — the header record that identifies a particular series (e.g., “DUDE” for our example).
- Number Series Lines — the lines that actually hold the starting number, ending number, last used number, and date ranges.
The “Allow Gaps” Setting
There’s an important setting on Number Series Lines called Allow Gaps in Nos. that is often misunderstood. Erik emphasizes that this is not a reverse setting — unchecking it does not guarantee gap-free numbering. Rather, enabling “Allow Gaps” is a performance enhancement. When turned on, Business Central does not table-lock the Number Series Lines record the same way, which prevents the number series from becoming a bottleneck in high-transactional environments. The trade-off is that gaps may appear in your numbering sequence.
Building a Sample App: The Dude List
Erik demonstrates with a simple custom app called “The Dude List” — a table with a number, name, and city, along with a list page and a card page. Initially, numbers are entered manually. The goal is to wire up automatic numbering via Number Series.
The Dude Table
table 54300 Dude
{
Caption = 'Dude';
DataClassification = ToBeClassified;
fields
{
field(1; No; Code[20])
{
Caption = 'No';
DataClassification = ToBeClassified;
}
field(2; Name; Text[100])
{
Caption = 'Name';
DataClassification = ToBeClassified;
}
field(3; City; Text[50])
{
Caption = 'City';
DataClassification = ToBeClassified;
}
}
keys
{
key(PK; No)
{
Clustered = true;
}
}
trigger OnInsert()
var
Setup: Record "Dude Setup";
NoMgt: Codeunit NoSeriesManagement;
Ref: RecordRef;
begin
Ref.Open(DATABASE::Dude);
if No = '' then begin
Setup.Get();
No := NoMgt.GetNextNo(Setup."No. Series for Dude", WORKDATE, true);
end;
end;
}
The Dude List Page
page 54300 "Dude List"
{
ApplicationArea = All;
Caption = 'Dude List';
PageType = List;
SourceTable = Dude;
UsageCategory = Lists;
CardPageId = "Dude Card";
layout
{
area(content)
{
repeater(General)
{
field(No; Rec.No)
{
ApplicationArea = All;
}
field(Name; Rec.Name)
{
ApplicationArea = All;
}
field(City; Rec.City)
{
ApplicationArea = All;
}
}
}
}
}
The Dude Card Page
page 54301 "Dude Card"
{
Caption = 'Dude Card';
PageType = Card;
SourceTable = dude;
layout
{
area(content)
{
group(General)
{
field(No; Rec.No)
{
ApplicationArea = All;
}
field(Name; Rec.Name)
{
ApplicationArea = All;
}
field(City; Rec.City)
{
ApplicationArea = All;
}
}
}
}
}
The Right Way: Use NoSeriesManagement Codeunit
When you want to get the next number from a Number Series, you might be tempted to directly query the No. Series and No. Series Line tables, figure out the last used number, and increment it yourself. Don’t do that.
Instead, use Codeunit 396 — NoSeriesManagement. Erik acknowledges that this codeunit is… extensive. It contains many procedures, some deprecated, and could benefit from refactoring. But the good news is that it boils down to one key function:
NoMgt.GetNextNo(NumberSeriesCode, Date, ModifySeries)
The parameters are:
- NumberSeriesCode (Code[20]) — the code of the Number Series to use (stored in your setup table).
- Date (Date) — typically
WORKDATEorTODAY. This matters if your Number Series has date-based lines (e.g., different number ranges for different months or periods). - ModifySeries (Boolean) — when
true, the function increments the “Last No. Used” on the series line. Whenfalse, it only peeks at what the next number would be without consuming it.
This single function handles all the complexity of finding the right line, incrementing the number, and respecting date ranges.
Creating a Setup Table
Following the standard Business Central pattern, you need a setup table to store which Number Series your app should use. This is the classic “single-record setup table” pattern with a blank Code[10] primary key:
table 54301 "Dude Setup"
{
Caption = 'Dude Setup';
DataClassification = ToBeClassified;
fields
{
field(1; PKEY; Code[10])
{
Caption = 'PKEY';
DataClassification = ToBeClassified;
}
field(2; "No. Series for Dude"; Code[20])
{
Caption = 'No. Series for Dude';
DataClassification = ToBeClassified;
TableRelation = "No. Series".Code;
}
}
keys
{
key(PK; PKEY)
{
Clustered = true;
}
}
}
The corresponding setup page auto-inserts the single record when opened, so users don’t have to manually create it:
page 54302 "Dude Setup"
{
Caption = 'Dude Setup';
PageType = Card;
SourceTable = "Dude Setup";
UsageCategory = Administration;
ApplicationArea = all;
layout
{
area(content)
{
group(General)
{
field("No. Series for Dude"; Rec."No. Series for Dude")
{
ApplicationArea = All;
}
}
}
}
trigger OnOpenPage()
var
no: Code[20];
begin
if Rec.IsEmpty() then
Rec.Insert();
No := 'AAA123123';
no := IncStr(No);
end;
}
Erik points out that in older versions of NAV, opening a card bound to an empty single-record table would automatically trigger an insert. That’s no longer the case in Business Central, so you need the OnOpenPage trigger to handle it.
Wiring It All Together
The key piece is the OnInsert trigger on the Dude table. When a new record is inserted and the No. field is blank, it fetches the next number from the configured Number Series:
trigger OnInsert()
var
Setup: Record "Dude Setup";
NoMgt: Codeunit NoSeriesManagement;
begin
if No = '' then begin
Setup.Get();
No := NoMgt.GetNextNo(Setup."No. Series for Dude", WORKDATE, true);
end;
end;
Note that the Setup.Get() call will error if no setup record exists. Erik mentions that in an OnInsert trigger this is acceptable — it simply tells the user that setup is missing. However, if you were in an event subscriber or other code where you don’t control when your code is called, an unprotected Get() would be a bad pattern. In production code, you’d want to wrap this in a meaningful error message.
A Tangent: How IncStr Works
Erik briefly demonstrates the IncStr function, which is the core mechanism behind Number Series incrementing. If you have a string like 'AAA123123', calling IncStr returns 'AAA123124'. It finds the trailing numeric portion of the string and increments it. Business Central loves number codes that combine letters and digits, and IncStr is what makes that work.
Understanding Delayed Insert
Erik explains an important UI behavior: when does the insert actually happen? This is controlled by the DelayedInsert property on pages.
- When
DelayedInsertis false (the default for card pages): the record is inserted into the database as soon as the user moves the cursor from the primary key field to any non-primary key field. - When
DelayedInsertis true (common on line subpages): the user must leave the entire record before it’s inserted.
In this example, since the Dude Card has one primary key field (No), the record is inserted as soon as the user tabs out of the No. field — even if they tab to the City field. That’s when the OnInsert trigger fires and the number is assigned.
A Note on “Saving” in the UI
Erik points out a subtle trap: Business Central’s UI sometimes shows “Saving…” when you enter data on a setup page, but this doesn’t necessarily mean the data is saved to the database. On the Dude Setup page, the Number Series selection isn’t committed until you actually leave the page. This caught Erik during the demo — the Number Series configuration appeared saved but wasn’t actually persisted yet, causing a “Number Series does not exist” error.
Watch Out: Page.RunModal in Transactions
Erik flags an important warning about the NoSeriesManagement codeunit. If you search for Page.RunModal within it, you’ll find places where the codeunit tries to pop up a UI dialog — for example, to let the user select a Number Series on the fly. This was added to make NAV friendlier for smaller companies (inspired by the C5 product in Denmark).
The problem: there are many places in the application where a number is fetched from a Number Series during an active database write transaction. In those contexts, displaying UI is not allowed. Instead of getting a helpful dialog, you get a large, awkward error message saying:
“Page.RunModal is not allowed in an active write transaction”
Nine out of ten times you see this error, it’s because the NoSeriesManagement codeunit is trying to show that selection dialog in a context where it simply isn’t permitted. It’s a known rough edge that hasn’t been fully cleaned up yet.
Summary
Implementing Number Series in your Business Central AL apps is straightforward once you know the pattern:
- Create a setup table with a
TableRelationto"No. Series".Codeso users can configure which Number Series to use. - Use
NoSeriesManagement.GetNextNo()— this is the one function you need. Pass it the series code, a date (typicallyWORKDATE), andtrueto consume the number. - Call it in your
OnInserttrigger when the primary key field is blank. - Never directly manipulate the
No. SeriesorNo. Series Linetables in code. - Be aware of “Allow Gaps” — it’s a performance setting, not a gap-prevention toggle.
- Understand
DelayedInsertto know exactly when yourOnInserttrigger will fire from the UI.