Success!

You've been added to the mailing list.

Subscribe to Newsletter

Get the latest Business Central insights delivered to your inbox!

Coming Soon!

This feature is coming imminently. Stay tuned!

Me, Myself and I are working hard to bring you something amazing.
Keep exploring and check back soon for updates!

BUSINESS CENTRAL 9 min read Nov 29, 2025

Surviving Business Central Breaking Changes using AL Preprocessor directives

Jeffrey Bulanadi

Jeffrey Bulanadi

Software Artisan, Visionary & Reverse Engineering Architect

Published on November 29, 2025

Preprocessor directives in AL: a practical, no BS guide

Yo BC Artisans,

For the past few weeks there’s been a lot happening in the BC space. I remember Microsoft holding back the BC27 platform upgrade for a while, and I believe one of the reasons was breaking changes.

Here’s the thing: Platform 26.5 is the last stable update before 27.0.

26.5 had a bunch of changes, but nothing broke. e.g. Invoice Post. Buffer posting routine, No. Series, and other events.

Instead of being removed or marked obsolete, they just stopped firing, which breaks stuff if you’re subscribed to them.

Normally, Microsoft would notify you that your environment can’t be upgraded because of some extension you need to fix. But that didn’t happen in 26.x. So if you added a custom field in G/L entry and subscribed to an event that fills that field, and that event no longer fires then yeah, we’ve got a problem.

Good news though: those issues are now officially treated as breaking changes in BC platform upgrade 27.

Why am I telling you this?

Because I want to share how you can handle these changes, breaking or not, in a smarter way. Microsoft does it too (try to notice their pattern), but we often ignore it. I used to, honestly.

If you’ve been writing AL for BC long enough, you’ll eventually need to ship the same app to slightly different worlds: production vs sandbox, feature on vs off, or old runtime vs new runtime.

So how do you deal with that?

Meet Preprocessor directives. They’re underrated, not really popular among AL devs, probably because most haven’t seen them in action.

TL;DR for beginners

  • Preprocessor directives are simple on/off switches at compile time.
  • You turn them on with names (called symbols) in app.json.
  • Use them for three things:
    • #if: include or exclude code.
    • #region: fold code for readability.
    • #pragma: temporarily hide a warning you already know about.
  • You choose the symbol names. Microsoft often uses CLEANXX e.g. CLEAN26, CLEAN27, and CLEANSCHEMA26 to control cleanup across versions.

If you only remember one thing: put a symbol in app.json, then wrap code with #if SYMBOL_NAME and #endif

What Preprocessor Directives Are

If you’ve ever seen AL code with lines that start with #, you’ve met preprocessor directives. They’re not real code that runs in your app, think of it as giving special notes to the compiler so it can set things up before looking at your real program code.

In simple terms: A preprocessor directive is like telling the compiler:

“Hey, before you start building the program, do this first.”

Think of them like little switches or notes you leave for the compiler. They help you:

  • Turn parts of your code on or off using
    #if, #elif, #else, #endif
  • Organize large files with collapsible sections using
    #region and #endregion
  • Hide or control warnings with
    #pragma warning disable <code>

Pretty handy, right? But there are a few things to know so you don’t run into surprises:

  • AL doesn’t give you built-in symbols like C#’s DEBUG.
    You must create your own symbols.
  • Symbols are super simple: they’re either defined (true) or not defined (false).
    No values, no data, nothing fancy.
  • You can define symbols globally in app.json or locally in a file using #define and #undef.
  • Regions and #if blocks can be inside each other, but they can’t overlap in weird ways. They need to stay properly nested.
  • And one important detail: user personalization and profiles do NOT use directives. These directives only work at compile time, not afterwards.

That’s really all there is to it. Preprocessor directives don’t change how users experience your app; they just help you control and clean up your code before it ever gets compiled.



Quick setup: define symbols (3 steps)

Think of a symbol as a simple flag you turn on. If it is listed in app.json, it is true everywhere. If not, it is false.

  1. Add the symbol name to preprocessorSymbols in app.json.
  2. Wrap code with #if SYMBOL and #endif.
  3. Build. If the symbol is present, the code compiles. If not, the code is skipped.

bcweekend-bc-01-001-app-json-symbol.png

Core building blocks (fast version)

Conditional: skip (Reference from actual Microsoft code)

bcweekend-bc-01-002-cash-flow-sample-page.png

Remember:

  • and, or, not are allowed.
  • Undefined symbol = false.
Regions: fold code (Reference from MS)

bcweekend-bc-01-003-regions-asm-line-mgt.png

Only for navigation. Does not affect compilation.

Pragmas: silence a warning briefly (Another reference from MS)

Use when you know a warning, accept it for now, and will fix later.

bcweekend-bc-01-004-pragma-warnings-copy-company.png

Rules:

  • Make the disabled block tiny.
  • Always restore.
  • Target only one warning code.

Tiny pattern: disable → place the few lines → restore. Don’t wrap the whole file.

#pragma warning , a simple way to see it

Think of the compiler like a smoke alarm in your kitchen. When it detects smoke, it goes beep beep beep!, that’s a warning.

But sometimes you’re just making a fried rice, a toast or whatever you are cooking, and you know it’s fine. You don’t want the alarm screaming while you finish cooking.

So you press the silence button:

That’s #pragma warning disable → “Stop beeping for this warning right now.”

Later, you let the alarm go back to normal: That’s #pragma warning restore → “Okay, beep again if you detect smoke.”

Code example

#pragma warning disable AL0468 // Silence this warning
// Code where the warning is ignored
#pragma warning restore AL0468 // Let warnings work again

In short:

  • Disable = mute the alarm for a specific warning.
  • Restore = unmute it so you don’t miss real problems.

Demo: Handling NoSeriesManagement → "No. Series" breaking change

This is grab code from my actual repo (of course, I stripped out the sensitive stuff).

So here’s one of the big ones: Microsoft split and modernized number series starting in BC24 with the new Business Foundation module.

The old NoSeriesManagement codeunit is on its way out. If you’re on BC27, it’s gone.

The replacement codeunit is "No. Series" and "No. Series - Batch" if you’re pulling lots of numbers at once.

How to survive it (2 quick steps):

Say you’ve got a data entry codeunit that needs numbers. Before, you’d call NoSeriesManagement. Now it’s swapped out for No. Series.

  1. Build a small wrapper codeunit (the “bridge”) with #if CLEAN26 inside.
    That way, the bridge decides whether to call the old NoSeriesManagement or the new No. Series depending on your target version.

  2. Flip the switch in app.json when you’re ready for BC27.
    Just add "CLEAN26" to preprocessorSymbols so the compiler uses the new path everywhere.

Usage:

bcweekend-bc-01-004-clean-noseries.png

Demo: Invoice Posting Buffer change ("Invoice Post. Buffer" → "Invoice Posting Buffer")

Here’s another real sample from my repo.

We’re adding a custom field to G/L Entry and also to Invoice Post. Buffer.

Since Invoice Post. Buffer is being deprecated, we need to add the same field to the new table Invoice Posting Buffer as well.

This one changed around BC26.

The old table Invoice Post. Buffer got replaced by the new Invoice Posting Buffer.

Stuff that used to run as events on the table was moved into separate codeunits.

If you want your code to work on both versions, just put #if / #else around your calls.

Upgrade path in 2 steps:
  • Add CLEAN26 in app.json when targeting the new buffer.
  • Subscribe to the new events only when CLEAN26 is defined; otherwise keep the legacy path.

bcweekend-bc-01-005-clean-invoice-post-buffer.png


Versioned cleanup with CLEANxx and CLEANSCHEMAxx

Microsoft’s Base App uses versioned symbols like CLEAN26, CLEAN27 and CLEANSCHEMA26 to control when obsolete code turns into removed code, and when schema can be dropped.

AL does not define these automatically. You choose when they are active by adding them to your app.json or CI build config.

i.e, keep a field until schema cleanup in 26

bcweekend-bc-01-006-cleanschema.png



Gotchas (stuff I learned the hard way)

  • AL doesn’t give you any built‑in symbols. If you need one, define it yourself.
  • You can’t overlap regions with #if blocks. If you need both, just nest them.
  • If you #pragma warning disable and don’t restore it, it stays off until the end of the file. Keep that scope tight.
  • Directives only work at compile time. They won’t change at runtime based on tenant or environment.

Pocket reference

  • Conditional: #if, #elif, #else, #endif, #define, #undef.
  • Operators: and, or, not.
  • Regions: #region#endregion.
  • Pragmas: #pragma warning disable|restore <ID>, #pragma implicitwith disable|restore.
  • Define symbols: app.jsonpreprocessorSymbols; or per-file with #define/#undef.

FAQ (quick answers)

  • Do symbols turn on automatically when I upgrade? No. You must add them to app.json or set them in your build.
  • Can I give symbols values? No. A symbol is either defined (true) or not (false).
  • Is #region only for looks? Yes. It does not change the compiled code.
  • Is it safe to disable warnings? Only if you scope it to the smallest possible place and restore it after.

Final thoughts

  • You can run your code in many versions, just use #if to handle the differences.
  • Copy Microsoft’s style for symbols, like CLEAN26 or CLEAN27. Simple names are fine.
  • Think of #pragma warning like a smoke alarm. You can mute it or unmute it, but only for a short time. Always know what you’re doing when you switch it off.

Support for the Community

I really appreciate any comments, good or bad. That’s how we learn from each other, right?

Drop a note and you’ll definitely keep me smiling. LOL, seriously, thank you.

If sharing this doesn’t take much of your time, I’d be grateful. It helps spread the knowledge and keep the love going for the Microsoft Dynamics 365 Community.

And if you want to see more stuff I post beyond Business Central, give me a follow: Jeffrey Bulanadi

Demo Repository

All the code and screenshots are up on my GitHub.

Check it out here: How to survive breaking changes using Preprocessor directives in AL

Sources worth bookmarking

Tags

#BCWeekendCodeHacks #MSDyn365BC #ALDevelopment #BusinessCentral #LearningBCNotSoBoringSeries

Share this article

Join the Conversation

Share your thoughts, ask questions, or discuss this article with the community. All comments are moderated to ensure quality discussions.

Leave a Comment

Your email is secure and will never be published or shared.

Basic HTML tags supported

0/2000 characters

Community Guidelines

  • • Keep discussions professional and on-topic
  • • No spam, self-promotion, or off-topic content
  • • All comments are moderated before publication
  • • Technical questions are especially welcome!