Best Practices for (Per Tenant) Extensions | Protect Yourself

Time to get back to Best Practices for Per Tenant Extensions.

This time we are going to discuss something that in my opinion should also be implemented by ISV’s in their AppSource solutions.

By default, AL Objects are extensible. This means that everyone can take a dependency on your extension and therefor Microsoft does not allow you to refactor any code once it lands on AppSource.

The solution is simple, but since it’s manual it requires extra discipline.

My recomendation would be to, by default, make all tables, pages and codeunits extensible = false and access = internal.

This means others cannot reuse your code and therefore you can change signatures of procedures, rename them and refactor your code.


table 50100 MyTable
    DataClassification = ToBeClassified;    
    Extensible = false;
        field(1; MyField; Integer) { }
    internal procedure MyProcedure()    
page 50100 MyPage
    PageType = Card;
    ApplicationArea = All;
    UsageCategory = Administration;
    SourceTable = MyTable;
    Extensible = false;

                field(Name; Rec.MyField) { }

codeunit 50100 MyCodeunit
    Access = Internal;
    trigger OnRun()
        myInt: Integer;

If you are an ISV, your reselling and implementationpartners can request an object to be opened up if they have a business reason for it.

Read More

If you want to read more about my Per Tenant Best Practices you can read previous posts.

Why best practices for Per Tenant Extensions?

One Per Tenant Extension to ‘Rule Them All’

Organizing your “big PTE” in Micro Services

PreProcessorSymbols & Per Tenant Extension Best Practices

Extending the same object twice in one Extension

Do you have feedback?

I love it when people have feedback and enjoy answering questions.

What I don’t like is polarization and social media cancel culture. Everybody has the right to their opinion and eveyone has the right to make mistakes and learn from it. Me included.

If you have to assign an advisory board, would you have a group of people with the same option that just say “yes” or would you like to be challenged with different opinions?

Again, with love and enjoy your “Sinterklaas” weekend


Oh, TempBlob! What did you do?

The alternative title for this blog post would have been something like… TempBlob, why did you waste my time! Or waste thousands of hours accross our community.

The topics of my blogs tend to be about what happens in the freelance projects I work on, and last week this was two extensions that have a substantial size (1000+ objects) that had to be BC19 Compatible.

BC19 is the first version of Business Central where warnings about obsoleted objects became errors. The most commonly used are TempBlob and Language.


Language is easy. Functions that used to exist in the table moved to a codeunit and the codeunit has the same name.

In both projects doing a Find/Replace on Language: Record with Language: Codeunit was enough.

Unfortunately for those who use Hungarian Notation, You also have to change your variable names.


This one is a lot more difficult. Not because the Codeunit has a Space in the name, but because the nature of the “Blob” field.

In Saas, the Blob field is the only way to create streams and it requires quite a bit of coding around to work with the obsoleted troubles.

The “Fix”

In both projects I fixed it by creating a new table called “TLA TempBlob” where TLA stands for the Three Letter Abbreviation of the partner on AppSource.

This new table looks like this

table 50500 "PTE Blob"
    TableType = Temporary;
    DataClassification = ToBeClassified;


    field(1; "Primary Key"; Code[1]) { }

    field(2; Blob; Blob) { }

    key(Key1; "Primary Key")  { Clustered = true; }

procedure MoreTextLines(): Boolean

    IF NOT ReadLinesInitialized THEN

    EXIT(NOT GlobalInStream.EOS);

procedure ReadTextLine(): Text
    ContentLine: Text;
    IF NOT MoreTextLines THEN


procedure ReadAsText(LineSeparator: Text; Encoding: Textencoding) Content: Text

    InStream: InStream;
    ContentLine: Text;

    Blob.CREATEINSTREAM(InStream, Encoding);


        Content += LineSeparator + ContentLine;

procedure WriteAsText(Content: Text; Encoding: Textencoding)
    OutStr: OutStream;
    IF Content = '' THEN

    Blob.CREATEOUTSTREAM(OutStr, Encoding);

procedure StartReadingTextLines(Encoding: TextEncoding)

    Blob.CREATEINSTREAM(GlobalInStream, Encoding);
    ReadLinesInitialized := TRUE;


    GlobalInStream: InStream;
    GlobalOutStream: OutStream;
    ReadLinesInitialized: Boolean;
    WriteLinesInitialized: Boolean;

I know that I am not the only one with this solution. All accross AppSource each App has it’s own new TempBlob table, simply because a Codeunit does not allow the use of the Blob fieldtype as variabletype.

TableType = Temporary

The reason Microsoft obsoleted TempBlob is to prevent people to declare this object without the Temporary tag.

When this happened TableType Temporary did not yet exist.

Now this is the case.

Other Changes

There is one other thing I ran into that I wanted to share.

On a lot of pages, Name 2 and Description 2 are added by Microsoft InVisible. They also removed a few fields.

Removing meant I ran into an issue with AddAfter. This was solved by changing to AddLast, following the Per Tenant Best Practices that you can find elsewhere on this website.

Thank you, with love…


“GENERIC METHOD” | Brilliant or Anti Pattern?

I’ve been in doubt if I should write this post or not. Read it fast as it may disapear if I regret writing it.

Ever since I started working with Navision, almost 25 years ago, I’ve had my own little stuborn ideas. These ideas got me where I am today, but just as often they got me in big trouble and caused personal relationships to shatter right before my eyes.

I wrote a lot about the legacy of Navision behind Business Central, the good, the bad and the ugly.

Today I want to talk about events, and why they are bad.


Events are bad? But… events are the whole backbone of the extensibility model. How can they be bad.

In order to understand that we first need to talk about Interfaces and Extensible Enums.

Progress in any development language or software framework can cause what was good yesterday to be something to avoid tomorrow, or even today.

Vedbaek, a few years ago…

Let’s rewind the clock a few years. Imagine C/Side with Hooks and Delta Files.

If this does not ring a bell, you are probably not old enough to understand this part of the blog and you can skip to the next paragraph.

A few years ago, and I’ve written many articles about this, Microsoft choose one of the SMB ERP Systems to go to the cloud. They only wanted to invest in one, not in three. Dynamics NAV was the choosen one.

The cloud needed a more mature extensibility model, and NAV had Delta Files and Hooks. This was choosen as the bases for the extension model we have today.

Part of this model was built in C/Side, which ended up being what we now know as “events”. Other parts were built outside C/Side and are what we now know as Table Extensions and Page Extensions. The first version did not offer an IDE for these objects and were tidous to work with.

What happened after that is history. The model grew into a new compiler that is compatible with Visual Studio Code and half a million events were added to a 30 year old application.

1.800 Apps in Microsoft AppSource are built on this model and used everyday.

So why is that bad?

It’s not bad, per se. But it is very tidious and it makes the framework very difficult to work with for junior developers.

Finding your way in thousands and thousands of events require very thourough knowledge of the old Navision application. Since there are only “few” of those it puts high constraignts on the growth of our ecosystem and make salaries for experienced developers go skyrocket.

Events have in between each other no relationship whatsoever. A few weeks ago I was talking to a friend who tried to enhance the Item Tracking module and he had to subscribe to 30+ events accross the system to complete a task.

In another case I was consulting a group of freelancers. They complained that they could never go to AppSource because they had heavily customized the Sales and Purchase posting processes.

My response, as a joke, was that Microsoft has built in the Generic Method pattern to override these posting routines with your own copy. The reason for making it a joke is that I thought (naive a girl as I am) that no sane developer would ever consider doing this.

Their response, to my surprise, was just a “thank you for this great suggestion, we will implement this”.

A third real life story, was a small consultation I did for a partner in Germany that offers a payment add-on that is very succesful. They are on AppSource and started to find out that aparently their App is not compatible with all of the other Apps. In other words, other Apps break their solution.

The reason for this is the “Handled Pattern” which is part of the Generic Method pattern which by itself is also an Anti Pattern but the only solution we had until the introduction of interfaces.

If two Apps subscribe to the same event and one handles it before the other get’s chance… the system fails.

And when someone decides to “override” posting routines the events in the original code are skipped.

Interfaces to the rescue

In my humble opinion, events should be marked as “obsolete pending” in favour of interfaces.

For Example: Sales Post

In Business Central, a Sales Document can have a few types such as quote, order, invoice or credit memo. There are a few more and partners can add new ones.

In my opinion a Sales Document Type should have methods that are implemented as part of an interface, such as “Release”, “Calculate Discount”, “Print” or whatever. Anything that is an action on a page.

If a partner really wants to override how the system works (which is bad enough to start with) they are then required to make their own Document Types. This shows a clear intention that they want the system to behave differently and it also allows other apps to auto-detect if they are compatible with this new implementation.

A Payment System, like the German one, should also replace the Payment System from Microsoft if they think they can do a better job.

Someone making a new Sales Document Type can still call the orriginal Payment Interface in the right places and allow other Payment systems to run nontheless.

Keep on dreaming Marije

A girl can dream right? I fully understand that the above situation will never happen.

Business Central was built on Navision and is it’s own legacy system and events, once our favorite, is now something from the past that is replaced with a better alternative.

Microsoft can never replace the events in the Base App with proper interfaces. The code is simply to old and events are all over the place.

Another problem is that an event publisher is married into it’s caller object. I remember in the very first discussions I had with Thomas Hejlsberg I suggested that an event should be able to move around when refactoring requires without breaking it’s subscribers. Unfortunately this never got implemented.

What about ISV’s?

Microsoft is always ahead of the game when compared to ISV’s. In the last releases of PrintVis we released a total of four interfaces that all serve a functional purpose. If a user or a partner of PrintVis is unhappy with how the interface behaves, they can imlement their own version.

If you have read my thoughts on best practices for Per Tenant Extensions you should have also seen that I don’t recommend anyone other than Microsoft or an ISV to work with Events, Enums or Interfaces.
If I were to do a code review of a Per Tenant Extension for an end-user and I would find any of these three I would put it into my report as “bad unless you have a damn good reason”.

This makes both this blog post and the (Anti) pattern a waste of time I guess.

Back to real life…

With love,