C/AL (Microsoft Navision)
The Pascal-derived business language of Navision and Dynamics NAV: a database-first dialect where every statement lives inside a trigger, records are a primitive type, and a generation of ERP consultants built entire businesses on it.
Created by Developed at the Danish company PC&C A/S, later Navision Software and then Microsoft. Its predecessor language, AL, shipped in Navigator/Navision 3.0 in 1990; the original C/AL compiler, runtime and IDE are credited to Michael Nielsen
C/AL — Client/Server Application Language — is the programming language of Navision, the Danish accounting system that Microsoft bought in 2002 and shipped for another seventeen years, from 2005 onward under the name Dynamics NAV. It is a small, strict, Pascal-derived imperative language with a fixed type system, no classes, no inheritance, and no source files. What it has instead is the database: a Record is a primitive type, a table is an object with executable triggers on its fields, and reading, filtering and writing rows are single statements rather than a library call away.
It is worth being precise about the dates, because the product and the language have different birthdays. The Navision product line begins in 1987 with a multi-user accounting application called Navigator. The programmability begins in 1990, when Navigator 3.0 — sold internationally as Navision 3.0, for DOS and OS/2 — shipped a full IDE and a language called AL, designed explicitly not as a general-purpose development language but as a way for partners to adjust the business logic they had been sold. The name C/AL dates to 1995 and Navision Financials 1.0, the first Windows version, where the language was paired with C/SIDE, the Client/Server Integrated Development Environment. The 1990 design premise never changed. C/AL exists to modify an application that already works.
History and Origins
PC&C A/S — Personal Computing and Consulting — was founded in 1984 by three students from the Technical University of Denmark: Jesper Balser, Peter Bang and Torben Wind. Their first product, PC Plus (1985), was a single-user accounting package. Navigator, in 1987, made it multi-user, and it did so with a client/server design at a time when that was genuinely unusual for a PC business application.
The decision that made C/AL matter came with version 3.0 in 1990. Rather than shipping a configurable black box, Navision shipped the application source as editable objects inside the customer’s database, with an IDE to change them. This was the company’s competitive strategy expressed as a technical architecture: a small Danish vendor could not build every vertical feature the world needed, but a channel of resellers could, if the product let them. AL was the tool that made the channel possible.
Navision Financials 1.0 in 1995 rewrote the product for Windows and rebuilt the development story around C/SIDE and C/AL. The next decade was consolidation of the business rather than the language: a 1998 contract with the Danish government produced Navision Stat, PC&C had been renamed Navision Software in the mid-1990s, and in late 2000 Navision Software merged with its principal Danish rival, Damgaard. On 11 July 2002 Microsoft completed its acquisition of the merged company — announced that May — for a reported figure of approximately $1.45 billion in stock and cash, its largest acquisition to that point. Navision Attain became Microsoft Business Solutions–Navision and, from 2005, Microsoft Dynamics NAV.
Under Microsoft, C/AL’s execution environment changed far more than its syntax did. NAV 2009 introduced the RoleTailored client and a three-tier architecture, moving C/AL off the client and onto a .NET-based server tier where the application was translated and run as managed code. NAV 2009 R2 opened .NET interoperability. NAV 2013 removed the classic client for end users entirely. NAV 2016 added events and try functions. Through all of it, the language a developer typed remained recognizably the one from 1995.
Design Philosophy
The database is not a library, it is the language. C/AL’s central design choice is that a Record variable bound to a table is a primitive. You declare one, set filters on it, and iterate it, with no ORM, no result-set object and no query string:
Cust.SETRANGE("Country/Region Code", 'DK');
Cust.SETFILTER(Balance, '>%1', 0);
IF Cust.FINDSET THEN
REPEAT
MESSAGE('%1 owes %2', Cust.Name, Cust.Balance);
UNTIL Cust.NEXT = 0;
This is why C/AL feels productive to people who came to it from business software and alien to people who came to it from general-purpose programming. The things an ERP system does constantly — filter, iterate, validate, post — are one line each. The things general-purpose languages do constantly — define a type, compose a function, abstract over behavior — are hard or impossible.
There is no source file. C/AL code does not live in text files that you edit and compile. It lives inside objects in the database, entered through the C/SIDE Object Designer. Variables are not declared in code; you open a variable declaration grid and fill in name, data type and subtype. Objects can be exported to .txt or the binary .fob format for transport, but that is an export, not the working representation. The consequences were structural: no meaningful use of Git or any other source control, no diff that matched what you edited, no branching, and merging performed by specialized third-party tools against exported text.
Everything is a trigger. There is no main. Code hangs off events in the application’s lifecycle: OnInsert, OnModify, OnDelete, OnValidate on a table field, OnRun on a codeunit, OnOpenPage and OnAction on a page. The runtime decides when your code runs. Writing C/AL is largely a matter of knowing which of the application’s many hundreds of triggers is the correct place to put a given rule — knowledge that lived in consultants’ heads and in the base application’s own conventions rather than in the language.
Deliberately small. No classes, no inheritance, no interfaces, no generics, no closures, no pointers, no user-defined types beyond records and simple enumerations expressed as Option. This was not an oversight; it was the 1990 premise held for a quarter century. The intended author was a functional consultant who understood accounting, not a systems programmer, and the language was tuned to make the common business change easy and the clever abstraction unavailable.
Licensing as a language feature. Object ID ranges were enforced by the license file. Microsoft’s application occupied the low ranges, the 50000–99999 range was reserved for customer-specific objects, and a customer’s license granted a specific, purchased set of object IDs — typically a small allocation of tables in the customer range, with larger allocations of pages, reports and codeunits. You could not create an object the license did not permit. Few languages have made commercial licensing a compile-time constraint quite so directly.
Key Features
A fixed, business-oriented type system. The built-in types are chosen for accounting rather than generality: Decimal for money, Code for uppercase-normalized identifiers (part numbers, account codes) distinct from Text, Date, Time, DateTime, Boolean, Integer, BigInteger, Option, GUID, Blob, plus Record, RecordRef, FieldRef and Page/Report/Codeunit object references. The distinction between Code and Text alone tells you what the language was for.
Table triggers and validation. A table field can carry an OnValidate trigger, and assigning through VALIDATE runs it — which is how the base application keeps derived data consistent without a service layer:
// Table 18 Customer, field "Credit Limit (LCY)" - OnValidate()
IF "Credit Limit (LCY)" < 0 THEN
ERROR(Text001);
The difference between Cust."Credit Limit (LCY)" := 0; and Cust.VALIDATE("Credit Limit (LCY)", 0); — direct assignment versus assignment that fires business logic — is one of the first things a C/AL developer learns and one of the most common sources of subtle bugs.
Procedures with EXIT, and WITH for record scope. Functions are declared in the designer and read, in exported form, like Pascal:
PROCEDURE CalcLineDiscount@1(Amount@1000 : Decimal) : Decimal;
BEGIN
IF Amount > 10000 THEN
EXIT(Amount * 0.1);
EXIT(0);
END;
// WITH scopes a record so its fields can be named directly
WITH SalesHeader DO BEGIN
SETRANGE("Document Type", "Document Type"::Order);
IF FINDFIRST THEN
MESSAGE('First order: %1', "No.");
END;
The @1000 suffixes are the internal variable IDs C/SIDE assigns; they appear in exported text and are part of why the export format was never comfortable to hand-edit. EXIT is both return and return value. WITH — later deprecated in AL for good reasons about ambiguity — was ubiquitous in C/AL, because the base application’s field names are long and quoted.
Errors as transaction control. ERROR does not throw an exception a caller can catch; it aborts and rolls back the current transaction, which in an accounting system is usually exactly right. Genuine error handling arrived late: NAV 2016 added try functions, marked by a property on the function rather than by syntax, which return a boolean instead of aborting and are the only way to catch a .NET interop failure without losing the transaction.
Events, added in 2016. Publishers, subscribers, and business, integration and trigger event types, modelled on the .NET event pattern. Trigger events on table and page operations are raised by the runtime with no code required; business and integration events must be declared and explicitly raised. The purpose was blunt and stated openly by Microsoft: to let partners hook into the application without modifying its objects, so that upgrades stopped being merges. Events are the bridge concept between the C/AL world and the extension model that replaced it.
Text constants. Literal user-facing strings were declared as text constants with per-language variants attached, so the base application could ship in dozens of languages without conditional code. Localization was not a library; it was part of how objects were defined.
Evolution
| Milestone | When | What changed |
|---|---|---|
| Navigator | 1987 | The product line begins; multi-user client/server accounting, no programmability |
| AL in Navigator/Navision 3.0 | 1990 | Full IDE for DOS and OS/2; partners can modify shipped business logic |
| C/SIDE and C/AL | 1995 | Navision Financials 1.0 for Windows; the language gets its name |
| Microsoft acquisition | July 2002 | Navision a/s acquired for a reported ~$1.45 billion; product renamed Dynamics NAV in 2005 |
| Three-tier architecture | NAV 2009 | RoleTailored client; C/AL executes on a .NET server tier as managed code |
| .NET interoperability | NAV 2009 R2 (2010) | C/AL can instantiate and call .NET types |
| Server-only execution | NAV 2013 | Classic client retired for users; Dataports removed, Query objects added |
| Events and try functions | NAV 2016 | Customization without modification becomes possible in principle |
| AL and VS Code | 2018 | Text-based AL ships alongside C/SIDE; txt2al converts exported C/AL objects |
| End of C/SIDE | Business Central v14 → v15 (2019) | v14 (April 2019) is the last release containing C/AL; v15 (October 2019) ships without it |
The arc has a clear internal logic. Every significant change from 2009 onward was an attempt to solve the same problem: the upgrade merge. Because customization meant editing Microsoft’s objects, every new release forced a three-way reconciliation between the old base application, the new base application, and the customer’s modified version — an expensive, error-prone project that the channel repeated for every customer, every version, forever. Events (2016) were an attempt to make customization additive within C/AL. Extensions and AL (2018) were the admission that the model itself had to go.
The replacement, AL, is deliberately continuous with C/AL at the level of statements — the same IF/THEN, the same Record type, the same triggers, largely the same built-in functions — and completely discontinuous at the level of process. AL lives in text files, uses Visual Studio Code, works with Git, compiles to a package, and cannot modify the base application at all: it can only extend it through table extensions, page extensions and event subscribers. Microsoft’s txt2al tool mechanically converts exported C/AL objects into .al source, which handles the syntax and leaves the architecture — the part that actually needed changing — to the developer.
Current Relevance
C/AL is dormant in the strict sense: there is no version of the product in which you can write it. Business Central version 14, the April 2019 on-premises release, was the last to ship C/SIDE; version 15 in October 2019 did not include it. Nothing since has.
It is not, however, gone. On-premises NAV and BC14 installations continue to run in production, and their business logic is C/AL. Mainstream support ended on 10 January 2023 for Dynamics NAV 2018 and in October 2023 for Business Central version 14 on-premises; according to Microsoft’s lifecycle policy, extended support for NAV 2018 runs to 11 January 2028, which is the practical outer horizon for a supported C/AL system. Some organizations will run past it anyway, as organizations running ERP systems reliably do.
Where you will still encounter the language:
- Migration work. Every remaining NAV installation is a future migration project, and doing one requires reading C/AL fluently — not to preserve it, but to determine what a decade of accumulated modifications actually does before re-expressing it as extensions.
- Archaeology on customizations nobody documented. The characteristic C/AL artifact is a modified posting codeunit whose author left the partner firm in 2011. Reading it is the only way to recover the business rule it encodes.
- Learning AL. Business Central’s current language inherits C/AL’s semantics almost entirely. Understanding why AL has
Recordvariables,SETRANGE,FINDSET,VALIDATEand table triggers requires understanding the language those things came from.
What C/AL is not is a language anyone should learn to write new code in, or evaluate for a new project. There is no runtime to target, no toolchain being maintained, and no Docker image or hobbyist implementation — C/AL never existed outside the licensed Navision product, and there is no way to run it that does not begin with obtaining and installing a legacy commercial ERP system with a valid license file. That constraint is itself part of the language’s story: C/AL had no community outside its channel because it had no existence outside its product.
Why It Matters
C/AL is the best-documented example of a language designed around a business model rather than a computational one.
Nearly every distinctive property follows from a commercial decision made in Denmark around 1990. Code lives in the database because the vendor shipped editable source to every customer. There are no classes because the intended author was an accounting consultant. Object IDs are license-enforced because customization was something you bought by the unit. Code is a distinct type from Text because chart-of-accounts identifiers behave differently from names. The language is small, strict and unabstracted because the people writing it were solving one company’s invoicing problem, not building a platform. Judged as a general-purpose language, C/AL is impoverished. Judged as an instrument for letting a global reseller channel modify a shipped ERP application profitably, it worked for close to thirty years, which is longer than most language designs survive at all.
It is also a clean case study in technical debt as a business model. Making the base application editable was exactly what made Navision spread — and it meant that every customer’s system permanently diverged from the vendor’s, so every upgrade was a merge. That cost was invisible in 1990, tolerable through the 2000s, and fatal by the time cloud delivery required Microsoft to update everyone’s tenant on a schedule. You cannot ship a monthly SaaS update to an application each customer has rewritten. Events, extensions and finally AL were all the same fix applied with increasing force, and the eventual answer was to remove the capability that had been the product’s original advantage.
Finally, the transition from C/AL to AL is a useful counterexample to the idea that migrations fail because of syntax. Syntactically, the two languages are close enough that Microsoft shipped an automated converter. Migrations from NAV to Business Central were nonetheless multi-year projects, because what had to change was not the code but the relationship between a customer’s code and the vendor’s — from editing to extending. txt2al could rewrite the statements. It could not tell you which of the thousands of lines your predecessor changed in codeunit 80 still mattered.
Timeline
Notable Uses & Legacy
The Dynamics NAV / Navision base application
The largest C/AL program ever written was the product itself. Every table, page, report and codeunit of the shipped ERP application — general ledger, inventory costing, manufacturing, warehousing, jobs, fixed assets — existed as C/AL objects in the customer's own database, readable and editable in C/SIDE by anyone with the right license granules. This is the single fact that defines the language's culture: the vendor's source code was not a reference implementation you consulted, it was the code running in your system, and you could change it.
Navision Stat (Danish central government)
Following a 1998 contract between Navision Software and Økonomistyrelsen, the Danish government's agency for financial management, Navision Stat was built on Navision Financials to replace the state's centralized accounting system. It is frequently cited as one of the rare large public-sector IT projects delivered on time and on budget, and it made C/AL the implementation language of Danish government accounting — a role it held long after the language stopped being current.
LS Retail's LS Nav
LS Retail, an Icelandic ISV, built LS Nav — point of sale, store operations, back office and head office functions — as a vertical solution on top of Dynamics NAV, deployed across retail, forecourt and hospitality businesses for approximately two decades. It is the clearest example of the C/AL ISV model: not an integration against an API, but a large body of C/AL objects merged into and shipped alongside Microsoft's own application. In 2019 it was rebranded and rebuilt as LS Central on Business Central, and LS Nav was subsequently discontinued.
The NAV partner and VAR channel
Navision was never sold directly at scale; it was sold through a channel of value-added resellers who implemented, customized and supported it. C/AL was the trade skill of that channel — reportedly thousands of consultants at hundreds of partner firms, whose product was a modified copy of the base application. The economics of that channel, more than any technical property, explain why the language looked the way it did and why replacing it was such a disruptive event.
Mid-market ERP customizations worldwide
The typical C/AL codebase was, by the accounts of partners and consultants who worked in it, not a product but a single customer's divergence from the standard application: a modified posting routine, a bespoke pricing table, an industry-specific document flow. Because these changes were made by editing Microsoft's objects in place, each upgrade required a three-way merge between the old base, the new base and the customer's version — the recurring cost that events, extensions and ultimately AL were all designed to eliminate.