Are you Preprocessing your AL Code?

In this video, I’m showing how to use the preprocessor directives in AL. With #directives you can also control how your code is processed by the compiler.

https://youtu.be/f3Gir_dHcZw

In this video, Erik explores the AL preprocessor in Business Central — a feature that brings decades-old compilation techniques from the C programming language into the world of AL development. He demonstrates how preprocessor directives can be used to conditionally compile code, manage AppSource vs. on-premises builds from a single codebase, organize code with regions, and suppress compiler warnings with pragmas.

A Brief History: Preprocessors in C

The concept of a preprocessor is not new. It was born in the C programming language around 1973 and has been a staple of C and C++ development for nearly 50 years. The preprocessor works by inserting commands into your source code that instruct the compiler what to include or exclude based on certain conditions.

Erik demonstrates this by showing a C header file from the Lua programming language source code. In it, you can see classic preprocessor patterns: #ifndef guards to prevent double-inclusion of header files, #define macros to create constants and inline function replacements, and #if/#else/#endif blocks to conditionally include debug or release code.

While the AL preprocessor doesn’t offer the full power of C’s preprocessor (no macros or includes), the inspiration is clear, and the core conditional compilation concept translates directly.

Understanding Preprocessing

Before diving into the AL specifics, it’s important to understand what “preprocessing” actually means. Preprocessing is the manipulation of your source file before the compiler processes it. Everything marked with the # symbol is pure text manipulation — it’s not compilation, it’s text processing that happens before the compiler takes its turn.

Conditional Compilation with #if, #else, and #endif

The most powerful feature of the AL preprocessor is conditional compilation. You can use #if, #else, and #endif directives to include or exclude blocks of code based on whether a symbol is defined.

Here’s a simple example using a page extension:

pageextension 50112 CustomerListExt extends "Customer List"
{
    trigger OnOpenPage();
    begin
#if something
        Message('App published: Hello world !!!');
#else
        Message('Something else!');
#endif
    end;
}

If the symbol something has not been defined, the preprocessor will include only the #else branch, so running this page would display “Something else!”. Once something is defined, the first message — “App published: Hello world !!!” — becomes the active code.

Beyond #if and #else, you can also use #elif (else-if) to chain multiple conditions together, creating a pseudo-case statement. And you can use #define and #undef to define and undefine symbols within a file.

Defining Symbols: Local vs. Global

There are two ways to define preprocessor symbols in AL:

Local Definition (Within a File)

You can use #define at the top of a file to define a symbol that is scoped to that specific file (object):

#define something

Global Definition (Across the Entire App)

To define a symbol across your entire application, add it to the preprocessorSymbols array in your app.json file:

{
  "id": "bde67c68-1c64-40a7-92bf-aceddf45a446",
  "name": "Preprocesor",
  "publisher": "Default publisher",
  "version": "1.0.0.0",
  "runtime": "7.0",
  "preprocessorSymbols": ["appsource"]
}

This makes the symbol available in every AL file in the project, which is much more practical for controlling build-wide behavior.

Real-World Use Case: AppSource vs. On-Premises Builds

One of the most practical applications of the preprocessor is maintaining a single codebase for both AppSource and on-premises deployments. AppSource apps use different number ranges than PTE (per-tenant extension) apps, and you may also have features that are only available in the cloud.

Here’s how Erik handles different table IDs for AppSource vs. on-premises:

#if appsource
table 70050100 Preprocessor
#else
table 50100 Preprocessor
#endif
{
    Caption = 'Preprocessor';
    DataClassification = ToBeClassified;

    fields
    {
        field(1; Pre; Code[20])
        {
            Caption = 'Pre';
            DataClassification = ToBeClassified;
        }
        field(2; Pro; Text[30])
        {
            Caption = 'Pro';
            DataClassification = ToBeClassified;
        }
    }
    keys
    {
        key(PK; Pre)
        {
            Clustered = true;
        }
    }
}

When appsource is defined in preprocessorSymbols, the compiled app file contains table number 70050100. When it’s not defined, the table number is 50100. Erik verified this by inspecting the compiled .app file — simply changing the preprocessor symbol and recompiling produces a completely different object ID.

This approach is also useful when the cloud version of your app uses functions or APIs that aren’t available on-premises. Rather than maintaining separate codebases, you can conditionally compile those sections out for on-premises builds.

A Note on the AL Compiler

Erik points out that currently there doesn’t appear to be a way to pass preprocessor symbols as command-line parameters to the AL compiler (alc). The symbols must be defined either in the source files or in app.json. Being able to inject them as build parameters would be a welcome addition for CI/CD pipelines in the future.

Code Organization with #region

The preprocessor also provides #region and #endregion directives for code organization. Unlike the conditional directives, regions don’t affect compilation at all — they’re purely a visual aid that enables code folding in the editor.

fields
{
    #region Fields
    field(1; Pre; Code[20])
    {
        Caption = 'Pre';
        DataClassification = ToBeClassified;
    }
    field(2; Pro; Text[30])
    {
        Caption = 'Pro';
        DataClassification = ToBeClassified;
    }
    #endregion
}

With regions defined, you can collapse large sections of source code in Visual Studio Code, making it easier to navigate complex objects with many fields, triggers, or procedures.

Suppressing Warnings with #pragma

The final preprocessor feature is #pragma, which differs from the other directives in that it actually interacts with the compiler itself rather than performing text manipulation.

The most common use is suppressing specific compiler warnings. For example, if you have a field name that’s too long and triggers warning AL0468, you can selectively disable and restore that warning:

#pragma warning disable AL0468
        field(3; Preprocessoriscoolandfuntoplaywith; Integer)
        {
            Caption = 'Preprocessoriscoolandfuntoplaywith';
            DataClassification = ToBeClassified;
        }
#pragma warning restore
        field(4; Preprocessoriscoolandfuntoplaywith2; Integer)
        {
            Caption = 'Preprocessoriscoolandfuntoplaywith';
            DataClassification = ToBeClassified;
        }

In this example, the warning is disabled for field 3 but re-enabled for field 4. This gives you precise control — you can acknowledge a warning for a specific case without silencing it across your entire codebase. There’s also a pragma available for suppressing implicit with warnings, though Erik notes he hasn’t personally needed to use that one.

AL Preprocessor Directives Summary

  • #define / #undef — Define or undefine a symbol within a file
  • #if / #elif / #else / #endif — Conditional compilation blocks
  • #region / #endregion — Code folding regions (editor only, no compiler effect)
  • #pragma warning disable / #pragma warning restore — Suppress and restore specific compiler warnings
  • preprocessorSymbols in app.json — Global symbol definitions for the entire app

What the AL Syntax Highlighter Doesn’t Do (Yet)

Erik notes that some languages and editors will gray out the inactive branch of a preprocessor conditional — showing you visually which code will actually be compiled. The AL syntax highlighter in Visual Studio Code does not do this yet, but it would be a welcome improvement to make preprocessor usage more intuitive.

Conclusion

The AL preprocessor brings a proven technique from nearly 50 years of C development into the Business Central ecosystem. While it doesn’t offer the full breadth of C’s preprocessor (no macros or file includes), it covers the most important use cases for AL developers: conditional compilation for managing AppSource vs. on-premises builds, code organization through regions, and fine-grained control over compiler warnings with pragmas. If you’re maintaining apps that target both cloud and on-premises environments, the preprocessor is an invaluable tool for keeping a single, manageable codebase.