Collage of icons around a laptop screen, including gears, clouds, and a card labeled Design Patterns, illustrating extensibility design patterns.

Design Patterns for Extensibility

3 min read

When extending Business Central, using the right design patterns can be a game-changer. In this blog post, I will share valuable insights into best practices for achieving extensibility within Microsoft Business Central.

Why Old Patterns Still Matter

Old coding patterns and practices, while foundational, can also be outdated. The “management codeunit anti-pattern” was a prime example discussed. These so-called “code containers” were once efficient in legacy environments, but today, they tend to violate the single responsibility principle.

Here’s the problem: they’re tough to read, even tougher to maintain, and they cause issues during merges. Moving forward, the strategy is to replace these containers with smaller, purpose-driven units. By doing this, code becomes easier to manage and more predictable.

Newer patterns, like the “orchestra pattern,” help address these issues.

The orchestra pattern drawn out for IRS forms: an orchestrator calling contracts, each with its own implementation component

Think of it as a storyboard for visually breaking down logic. For instance, the pattern was used for 1099 tax forms in Business Central to ensure vendors’ invoices were accounted for. The orchestra defines the “story” using contracts and interfaces, keeping responsibilities clean and manageable.

The same pattern as three layers - consumer, orchestrator with its contract interfaces, and the implementing components

Example for Contracts:

The IRS 1099 interfaces for form box calculation, document creation and printing, with the enum that implements all three

Smart Testing Practices

Testing is key when building scalable apps. To help with test automation, Business Central allows the use of internalsVisibleTo in the app.json to grant test libraries access to internal code.

A Microsoft app.json using internalsVisibleTo to expose internals to its own test app

However, be cautious with this feature—it doesn’t make the app foolproof against tampering when IDs are hijacked. (you can create new app with same GUID and internals become visible to you 😁)

To stay on the safe side:

  • Remove internal visibility for production builds.
  • Use dedicated test libraries separate from the core application code.

This ensures sensitive code remains secure while still supporting robust testing practices.

Simplifying User Selections

Have you ever had a user request to limit dropdown lists to only a few relevant options? Microsoft has integrated a solution for this directly into Business Central. For example, on sales or purchase pages, temporary option lookup buffers can now clean up selection lists dynamically.

A Business Central Country/Region Code lookup listing every country in the table

The Sales Invoice Subform source, with the real Type field beside the filtered text field that replaces it

In the standard:

The filtered field definition, looking up Option Lookup Buffer and validating through AutoCompleteLookup

Table 1670 Option Lookup Buffer deciding which option values to include based on enabled application areas

This feature ensures dropdowns present only relevant options without overwhelming users with unnecessary selections.

The Opportunity Card using ValuesAllowed to restrict its Sales Document Type field to blank, Quote and Order

Here’s how it works:

  1. Extend the OptionLookupType enum.

An enum extension adding Service Location, Requisition Worksheet and Gen. Journal Document Type lookup types

2. Create two subscribers—one to set up the lookup and another to filter values based on context.

A subscriber to OnFillBufferLookupTypeCase, pointing the new lookup type at the Gen. Journal Line document type field

A subscriber to OnBeforeIncludeOption, allowing only blank, Invoice, Credit Memo and Finance Charge Memo

3. Replace the original list field with this filtered temporary field on the page.

A page extension showing AllowedValues commented out, because it cannot be set from a different app

The alternative: a custom text field added after Document Type, looking up through the Option Lookup Buffer

Temporary Tables Done Right

Temporary tables simplify data manipulation, but improper use can bog down system performance. The classic approach—loading all fields into temporary tables—carries baggage, especially in SaaS environments where shared resources matter.

What’s the smarter approach?

  • Only pull the fields you actually need. Use SetLoadFields to limit the data.
  • Use dedicated buffer tables with trimmed-down fields.

This reduces memory usage while keeping performance steady. For developers, the takeaway is clear: fetch less, process efficiently, and always prioritize scalability in your code.

Mastering Manual Subscriptions

Event subscription is central to Business Central extensibility, but not all events need to run all the time. That’s why “manual binding” is such a useful feature.

By default, events automatically bind and remain active. With manual binding, however, you gain more control by activating subscriptions only when truly needed. This reduces memory usage and speeds up processing.

Here’s how it works:

  • Use BindSubscription to activate the event manually.
  • Use UnbindSubscription when the transaction completes.

This pattern is especially useful for scenarios like preview posting, where collecting data mimics actual posting without executing full logic. It also ensures your code is clean and reusable.

In the standard:

Three posting codeunits stacked, each binding a subscription before calling the posting preview

The manual subscription chain drawn out, from the sales order through the posting preview to the event handler

Isolated Events

What if you need an event to run independently without affecting other processes? That’s where isolated events shine. Unlike regular events, isolated events run in a fresh transaction and commit their changes without impacting the main thread.

For example, you can use them to automate actions or reduce locking risks in multi-threaded systems. Though powerful, they must be handled with care due to their implicit commit behavior.

  1. Non Isolated Event

A procedure that raises a non-isolated event and then inserts a customer

A test asserting that the error thrown inside the non-isolated event aborts the whole call

The subscriber that throws the error inside the non-isolated event

2. Isolated Event

The same procedure written against an isolated event instead

A test asserting the customer is still inserted after an isolated event throws

The subscriber that throws the error inside the isolated event

3. Non Isolated Event After Isolated Event

The subscriber that throws the error inside the non-isolated event

A test asserting the customer inserted inside the isolated event survives a later non-isolated failure

The subscriber that throws inside the non-isolated event, again

The subscriber that inserts the customer inside the second isolated event

Conclusion

Design patterns may evolve, but their goal remains the same: to make software scalable, maintainable, and user-friendly. From managing temporary tables to using manual subscriptions, there are plenty of strategies developers can adopt to improve performance and align with modern extensibility standards.

By following these principles and staying open to new ideas like isolated events and orchestra patterns, you can write cleaner, smarter, and more efficient code. After all, the best systems aren’t just built—they’re designed.

What design patterns have you found most effective for extensibility? Share your thoughts!

Related posts

Comments

    Leave a comment

    Reviewed before it appears.