Monday, 5 October 2026

Composite Layouts in Business Central 29.0: Brand One Report End to End

 

Business Central 29.0 lets you stop baking branding into every Word layout. Instead of one file carrying structure, letterhead and typography all at once, you build three separate pieces and Business Central merges them when the report runs.

This walkthrough takes one report from plain to fully branded, then sets it as the default so you never repeat the work.

The nine steps: build the header/footer in Word → register it → approve it → create the theme → get a Body layout → assign both parts → run it → set defaults → do the same in AL.

Before you start: a 29.0 environment, Word on your machine, and the Cronus logo file.

What we are building

We will brand the Standard Sales - Invoice report for a company called Contoso. By the end you will have:

•      A header/footer part carrying the Contoso letterhead and a footer with the company registration line

•      A theme part carrying the Contoso fonts and colors

•      A body layout holding only the invoice structure

•      Both parts assigned, first to one layout, then as a company-wide default

what you build · 3 parts, merged at render

what you build · 3 parts, merged at render

Two pages do all the work. Report Layouts holds layouts with subtype Default and Body. Manage themes and header-footer layouts holds the ones with subtype HeaderFooter and Theme. A layout appears on exactly one of them, decided by its subtype.

Step 1. Build the header and footer in Word

Start with a blank Word document. This file will carry nothing but page furniture, so do not add any invoice content to it.

Go to Insert > Header, pick a style, and drop in the Cronus logo. Then Insert > Footer for the address.

Save it as Contoso-External-HF.docx. Plain .docx, not a template.




Step 2. Register the header/footer part in Business Central

Press Alt+Q, type Manage themes and header-footer layouts, and open the page. This page is empty of your own parts on a fresh environment, though Microsoft's shipped designs are already listed.

Choose New header/footer. Enter a Name and Description, then OK.

Select the Contoso-External-HF.docx file you saved. The part is now registered.



Step 3. Approve the part before you try to use it

This is the step that catches everyone. A freshly created part cannot be assigned to anything yet. Try it and you get:

The part Contoso external letterhead is not approved. Only approved themes and header/footer parts can be assigned. Approve it in Report themes and header-footer setup first.

Open Report themes and header-footer setup and click action Set as approved. Then go back.

It is worth understanding why this gate exists rather than clicking through it. These parts are shared. One of them can end up on every outgoing document in the company, so the approval step is what stops a half-finished letterhead from reaching a customer. In a real rollout, treat approval as the point where someone who owns the brand signs off, not as a checkbox the developer ticks.



Step 4. Create the theme part

Same page, different action: New theme.

The important difference is the file type. A theme has to be a Word template, a .dotx file, not a .docx. That is the t in dotx doing its job. A theme carries fonts and a color palette rather than content, which is exactly what a Word template is for. Try to upload a .docx here and it will not take.

So in Word: set up your fonts and colors on the Design tab, then Save As and pick Word Template (*.dotx). Name it Contoso-Brand.dotx and register it as a new theme.

Approve it, same as step 3.


If you want to see what good looks like before building your own, open one of the shipped themes first. Microsoft ships three: Calm, Playful, and Standard, alongside the Default. Calm is muted and green, Playful adds color and gradients. Export one, look at how it is put together, then build yours from that starting point rather than from blank.


Step 5. Get yourself a Body layout

Here is the part that trips people up on day one, so read it before you start clicking.

A theme and a header/footer can only be assigned to a layout whose subtype is Body. Not Default. The difference matters because a Default layout is a complete document that already owns its own header, footer and styling, so there is nothing for the merge to add. A Body layout is deliberately incomplete and expects the other two parts to be merged in at render time.

On the Report Layouts page you will see the new Subtype column. Filter it and check what you actually have. Two things you will hit:

•      Select an RDLC layout and the composite actions are greyed out. This feature is Word only.

•      Select a Default layout and you get an error telling you the layout cannot carry a theme or header/footer.

If the report you want has no Body layout, the quickest route is to export the existing standard layout, then import it back as a new layout with the subtype set to Body. Open it in Word first and strip out the header, the footer and the hard-coded styling, because those are now the job of your two parts. What should be left is the invoice structure and nothing else.

Run it at this point, before assigning anything. You should get a very plain document with no letterhead and no branding. That is correct, and it is a useful checkpoint: if it still looks branded, you have not stripped the layout properly and you will get a doubled-up header later.


Step 6. Assign the theme and header/footer

Select your body layout on Report Layouts, open the Composite Layout menu, and choose Set report theme and header-footer.

Pick Contoso external letterhead as the header/footer part and Contoso Brand as the theme part. If either one is missing from the dropdown, it is not approved yet. Go back to step 3.




Step 7. Run it

Run the Sales Invoice and pick your body layout. The PDF now comes out with the Contoso letterhead on page one, the registration footer on every page, and your fonts and colors throughout. The body layout file itself has not changed since step 5. The merge happened at render time.


Step 8. Set defaults so you stop doing step 6

Assigning parts layout by layout works, but it does not scale past a handful. The defaults page is where this becomes genuinely useful.

Open Report defaults for themes and header-footer setup. You can set a default theme and header/footer at four levels, and the most specific one wins.

defaults cascade · 4 levels, most specific wins

defaults cascade · 4 levels, most specific wins

Worked through with our example:

1.    Global. Set Contoso Brand and the external letterhead here. Every document report in every company now uses them.

2.    Company. Contoso Denmark needs a different registration footer. Add a company-level row for it, pointing at a Danish header/footer part. Denmark overrides global; everything else still follows global.

3.    Report. Credit memos need a plainer treatment than invoices. Set a report-level row on the credit memo report.

4.    Layout. One specific body layout needs something unusual. Set it there, exactly as you did in step 6.

The multi-company case is where this earns its keep. Set Danish branding once on the Danish company and every document report in that company follows, with no per-report work at all. Add a Swedish company next year and it is one row, not a layout migration.


Step 9. The same thing in AL

Everything above has a code equivalent. Three properties do it, all documented in the AL Language extension changelog: Subtype on the layout, plus HeaderFooterPart and ThemePart on a body layout.

report 50100 "Contoso Sales Invoice"
{
    Caption = 'Contoso Sales Invoice';
    DefaultRenderingLayout = ContosoInvoiceBody;

    dataset
    {
        // data items here
    }

    rendering
    {
        layout(ContosoInvoiceBody)
        {
            Type = Word;
            Subtype = Body;
            LayoutFile = './Layouts/ContosoInvoice-Body.docx';
            Caption = 'Contoso sales invoice';
            Summary = 'Invoice structure only. Theme and header/footer merge at render time.';
            HeaderFooterPart = ContosoExternalHF;
            ThemePart = ContosoBrandTheme;
        }

        layout(ContosoExternalHF)
        {
            Type = Word;
            Subtype = HeaderFooter;
            LayoutFile = './Layouts/Contoso-External-HF.docx';
            Caption = 'Contoso external letterhead';
            Summary = 'Logo, address block, registration footer.';
        }

        layout(ContosoBrandTheme)
        {
            Type = Word;
            Subtype = Theme;
            LayoutFile = './Layouts/Contoso-Brand.dotx';
            Caption = 'Contoso brand theme';
            Summary = 'Corporate fonts and color palette.';
        }
    }
}

Note the file extensions in LayoutFile, because the compiler will hold you to them. Theme points at .dotx. Body and HeaderFooter point at .docx.

If you are building a branding extension rather than a report, you can ship nothing but parts. A report extension that adds only Theme and HeaderFooter layouts puts them into the shared pool for administrators to assign from the setup page:

reportextension 50101 ContosoBrandParts extends "Standard Sales - Invoice"
{
    rendering
    {
        layout(ContosoInternalHF)
        {
            Type = Word;
            Subtype = HeaderFooter;
            LayoutFile = './Layouts/Contoso-Internal-HF.docx';
            Caption = 'Contoso internal header/footer';
            Summary = 'Single rule line. For internal print and PDF.';
        }

        layout(ContosoCalmTheme)
        {
            Type = Word;
            Subtype = Theme;
            LayoutFile = './Layouts/Contoso-Calm.dotx';
            Caption = 'Contoso calm theme';
        }
    }
}

One 29.0 change makes reusable header/footer parts work far better than they otherwise would: Word layouts now include a company information dataitem shared across every report dataset. A shared letterhead needs the company address and registration number, and it can no longer assume each report declared them. That dataitem is why a single header/footer part can sit on fifty different reports and still print the right company details.

So the migration looks like this: build the body layout and parts alongside the old layout, assign and test, then retire the old layout rather than deleting it. If something is wrong, flip the status back. Nobody loses a document in the meantime.


Friday, 2 October 2026

Record.IsDirty in Business Central 29: a worked example

 

Business Central 29 adds IsDirty to the Record API. It lets your AL code ask a record whether anything has changed since it was loaded, so you can skip writes that aren't needed and stop tracking change state by hand. In this post I'll show how it behaves on a Customer Card, and where it fits in real code.

What IsDirty does

Record.IsDirty() tells you whether a record instance has changed since it was loaded. It returns true when the in-memory values differ from what came back from the database, and false when they still match.

procedure IsDirty(): Boolean

That gives you a clean way to decide whether a record needs validating, saving, or a prompt to the user, without keeping track of the state yourself.

If you've worked with RecordRef, this will look familiar. RecordRef.IsDirty() has been around since runtime 5.0. Version 29 brings the same capability to the Record data type, so you ask the question directly on the record you're already working with.


Seeing it on a Customer Card

The quickest way to see what IsDirty reports is a page action. Add this page extension to a sandbox, open any customer, and run the action.

pageextension 50100 "Customer Card IsDirty Demo" extends "Customer Card"

{

    actions

    {

        addlast(Processing)

        {

            action(IsDirtyDemo)

            {

                ApplicationArea = All;

                Caption = 'IsDirty Demo';

                ToolTip = 'Shows how Record.IsDirty reports changes on this customer.';

 

                trigger OnAction()

                var

                    Customer: Record Customer;

                    DirtyMsg: Label 'IsDirty after %1: %2', Comment = '%1 = step name, %2 = Yes or No';

                begin

                    Customer.Get(Rec."No.");

                    Message(DirtyMsg, 'Get', Customer.IsDirty());

 

                    Customer.Name := 'IsDirty test';

                    Message(DirtyMsg, 'changing Name in memory', Customer.IsDirty());

                end;

            }

        }

    }

}

Set runtime to 18.0 or later in app.json before you build it.


The first message shows No. The record has just been loaded and nothing has changed.




The second message shows Yes. The Name in memory no longer matches what's stored in the database.



Version and availability

IsDirty ships with Business Central 2026 release wave 2, version 29.

The runtime floor is 18.0. Compiling against runtime 17.0 produces diagnostic AL0666, stating that IsDirty is not available in that runtime version and that 18.0 or greater is required. Set runtime to 18.0 in app.json before you use it.

Thursday, 1 October 2026

Business Central 2026 Release Wave 2 (v29.0) Is Generally Available

 

Version 29.0 of Business Central is now generally available. If you create a new online environment today, 29.0 is what you get by default.


Spinning up a 29.0 sandbox

The quickest way to see this for yourself is the admin center. Go to Environments, choose New, and look at the Version dropdown. 29.0 is now sitting there as a selectable option rather than a preview build.


If you have been testing on a preview sandbox, read this

The preview sandboxes you created in September are on borrowed time. Microsoft deletes them automatically about 30 days after GA, which puts the cutoff somewhere in early November 2026. You also cannot update a preview environment to a different version, so there is no path from a preview sandbox to a supported one.

If you have test data, custom extensions, or configuration sitting in one of those, move it now. Export what you need and recreate the environment on the GA build of 29.0.


Links worth keeping

What

Where

What's new and changed in 29.0

Microsoft Learn

AI at Work Roadmap, filtered to Business Central

microsoft.com

Release Communications MCP Server setup

Microsoft Learn

What's new videos

aka.ms/BCLE

Partner PowerPoint deck for customer conversations

aka.ms/BCDeck

Office Hours calls

aka.ms/BCOfficeHours



Wednesday, 16 September 2026

NAV to Business Central: What Actually Moves, and What Needs to Change?


In Part 1 I argued that NAV, GP and SL customers should start planning now instead of waiting for a lifecycle date to force the decision.

The next question is always more practical:

"What actually happens when we move from Dynamics NAV to Business Central?"

Most people picture one of two extremes. Either it's a database upgrade, or it's a rebuild from scratch. Neither is right, and the space between them is where most project surprises live.

So let me start with the thing that clears up the most confusion.

It isn't one move. It's an upgrade, then a migration

There is no supported route that takes a NAV database straight into Business Central online. The cloud migration tool moves data from a Business Central on-premises database into an online tenant. NAV isn't a valid source for it.

That means every NAV customer is doing two projects that get discussed as one:

1.      An upgrade, from your NAV version to a Business Central on-premises version the cloud migration tool supports. Usually more than one hop.

2.      A cloud migration, from that on-premises database into Business Central online.

Microsoft's documented routes look like this:

Your current version

Route to Business Central online

NAV 2015 through 2018, and BC 13

BC 14, then BC 25, then cloud migration to online

NAV 2013 / 2013 R2

NAV 2018, then BC 14, then BC 25, then online

NAV 2009 SP1 / 2009 R2

NAV 2013 or 2015, then NAV 2018, then BC 14, then BC 25, then online

There are two mandatory waypoints on that route, and both catch people out.

Version 14 (the 2019 Spring release) is the last version that still understands the old development model, which is exactly why every path passes through it. Worth knowing: its mainstream support ended in October 2023, minor updates stopped, and new customers can't run it in production. It exists purely as a stepping stone.

Version 25 (2024 release wave 2) became mandatory more recently. Starting with 2025 release wave 1 (v26), direct upgrade from version 14 to the latest release stopped being supported, and the path now runs through v25. So you can't go from 14 straight to a current release. If your target is Business Central on-premises rather than online, add one more hop from v25 to the current version on top of that.

The practical consequence: your NAV version doesn't just affect effort, it determines how many technical upgrades you're doing before the migration even begins. A NAV 2018 customer is doing two, to 14 and then 25. A NAV 2009 customer is doing four. That's why "which version are we on" is the first question I ask, and why the answer moves the timeline more than the customization count does.

One caveat on the numbers above. Waypoints shift with release waves, and the v25 requirement itself only appeared with v26. Check the current supported upgrade paths on Microsoft Learn before you build a plan around any specific version.

What "moves" actually means

The cloud migration itself is data replication. You install an Integration Runtime next to your on-premises database, connect it to the online tenant, select companies, and run replication through Azure Data Factory. Initial load, then deltas, then a data upgrade on the online side, then you complete the migration so the online environment becomes the primary system.

Two things follow from that, and the second one catches people out.

Code does not replicate. Tables and fields exist in the cloud only because an extension in that tenant declares them. There is no mechanism that carries application objects across.

And your data is coupled to that code. Data from tables with code customizations can't be carried forward unless those customizations are handled by extensions installed both on-premises and online.

That second point deserves a second read. It isn't only "your C/AL has to be rewritten." Your data in customized tables has nowhere to land until the matching extension exists on both sides of the replication. A custom field on the Item table, or a custom table holding ten years of quality inspection records, stays behind until the AL extension defining it is installed in both environments.

If there's one sentence I'd want a NAV customer to absorb before anyone estimates anything, it's that one.

The real decision: full migration or reimplementation

Microsoft documents two paths, and choosing between them shapes the whole project.

Full migration. All data and customizations. Requires converting C/AL to AL extensions and upgrading through to a current version. This is what people usually mean when they say "migration."

Reimplementation. Essential business data only: master data, balances, a subset of posted historical entries, and setup. It doesn't require converting your extensions, and it doesn't require upgrading beyond version 14. There's a dedicated Business Central 14 reimplementation tool for it.

Both paths still require reaching version 14 first. That part isn't optional.

Both approaches are valid. The right choice depends on the customer's data, customizations, integrations and business requirements, and I've seen both work well.

Full migration tends to fit when:

  • Historical transactions are operationally necessary, for example serial and lot traceability, long-running service contracts, or warranty claims against old shipments.
  • Customizations are still central to how the business runs, so they need to exist in AL regardless.
  • Audit or statutory obligations depend on detailed history being available inside the ERP.
  • The third-party solutions in use have Business Central online equivalents.

Reimplementation tends to fit when:

  • History is needed for reference and can reasonably live in a reporting or archive system.
  • A meaningful share of customizations turn out to be workarounds, unused, or now standard functionality.
  • The business wants to standardize processes rather than carry existing ones forward.
  • Some customized tables have no clear business owner willing to fund converting them.

If the choice isn't obvious, estimate both. The comparison usually tells you more about the project than either estimate does on its own.

What I'd avoid is treating this as a technical footnote. It's a business decision about what the company actually needs in the new system, and it belongs in the first few conversations, not in a design document six months later.

What usually needs technical work

C/AL customizations

They don't run in Business Central. Not online, and not in modern on-premises versions either, since C/AL was removed from the product entirely. Everything has to become AL extensions, or has to be dropped.

That doesn't mean rewriting line for line. Some of what you customized in 2012 is standard functionality now. Some was a workaround for a limitation that no longer exists. Some nobody uses. I'll cover how I assess this in Part 3.

Reports

Many NAV environments carry years of customized reports. Before converting any of them, I answer two questions: is this report still being opened, and does Business Central already provide an equivalent? Report usage data from your existing system is more reliable here than asking users, who tend to say yes to everything.

Integrations

Integrations deserve their own workstream. NAV environments connect to banking, CRM, payroll, warehouse systems, shipping providers, tax portals and in-house applications. Anything that authenticated against an on-premises service, used a fixed internal endpoint, or relied on file drops on a shared folder needs rework for a cloud tenant.

I've seen a project where the data migration completed cleanly and the numbers reconciled, and users still couldn't work on day one, because an integration nobody had listed as in scope was the thing that actually fed daily transactions into the system.

Third-party solutions

This is the item most likely to set your timeline, and it's the one people discuss last. If an add-on isn't available as an app for Business Central online, you either replace it, rebuild the functionality, or reconsider the path. Check availability early, because the answer can change the whole plan.

Permissions and licensing

NAV permission sets don't carry across cleanly, and licensing works differently. Essentials and Premium are licensed per user, and license type assignment affects what people can do. Teams users with certain Microsoft 365 plans can get read-only access to Business Central data at no extra cost, provided the organization holds at least one Business Central license. This is worth modelling properly rather than discovering during user acceptance testing.

Job queues and scheduled processing

These don't arrive as working configuration. They get set up again, and the behavior differs from what your NAS-based processing did. Inventory them, because they're easy to forget and very visible when they're missing.

Localizations

Country and region specific functionality arrives in Business Central online through localization apps. Verify the localization you need is available for your country, and confirm that any locally customized tax, regulatory or statutory reporting logic has been converted into AL extensions that work with that localization. In my experience this is where India, and other markets with heavy statutory requirements, needs more attention than a generic project plan allows for.

Before you estimate anything

Part 1 listed the questions I'd ask during an assessment, so I won't repeat them here. But this stage adds three that are specific to the route:

1.      Which NAV version exactly, including build? This sets the number of upgrade hops, and each hop is a project with its own testing cycle.

2.      Does every customized table have an owner willing to fund its extension? If not, that data isn't moving, and the business needs to know now.

3.      Which third-party solutions have a Business Central online equivalent? Check before committing to a date.

Answer these and the migration stops being an open-ended question and becomes a scope you can price.

The bottom line

Moving from NAV to Business Central isn't a single event, and it isn't a choice between "everything moves" and "everything gets rebuilt."

It's an upgrade path to version 14 and beyond, followed by a data replication into the cloud, with one hard rule sitting underneath it: your data travels only as far as your code does.

Once a customer understands that, the interesting conversation starts. Not "can we migrate," but "what do we actually want to take with us."



Monday, 14 September 2026

How I Recreated the Extension Upload Experience in Business Central


Microsoft has deprecated the in-product upload on the Extension Management page and the Automation API extensionUpload endpoint. Both are scheduled for removal in 2027 release wave 1 (version 30). Extension Management is expected to survive only as a read-only list.

https://mohana-dynamicsnav.blogspot.com/2026/09/managing-apps-in-business-central-admin.html


The supported replacement is the Business Central admin center API so I built an app that calls it from AL.


PTE Extension Management gives you a page that looks and behaves like the one you already use:

  • Installed Extensions (Admin Center) live list of what is installed, with available update versions and scope.
  • Upload Extension — pick a .app, choose when to deploy (current version, update window, next minor, next major), set the schema sync mode, and accept the publisher terms.
  • Uninstall — with an optional, confirmed delete of the extension's data.
  • Installation Status, Scheduled Installs, and per-extension Operations so you can see what the API actually did.
  • Deployment Log — every attempt, with user, environment, HTTP status, operation ID, and the raw response body.

Setup is a one-time job:

Step 1. Create the app registration

Go to the Azure portal, then Microsoft Entra ID > App registrations > New registration.


Step 2. Copy the Application (client) ID - you will paste this into Business Central

Step 3. Create a client secret

Go to Certificates & secrets > Client secrets > New client secret.


Step 4. Add API permissions

Go to API permissions > Add a permission > APIs my organization uses, and search for Dynamics 365 Business Central.

Choose Application permissions, and select the permission that grants admin center API access for your tenant.

Then select Grant admin consent for [tenant] and confirm. The Status column must show a green tick for every permission before you continue.


Step 5. Register the app in Business Central

In Business Central, use Tell Me to open Microsoft Entra Applications. Select New.

  • Client ID: paste the Application (client) ID from Step 2

 


Step 6. Assign Exten. Mgt. - Admin

On the same card, in the User Permission Sets subpage, add a line and select Exten. Mgt. - Admin.

State: set to Enabled

Step 7. Grant consent

Select Grant Consent on the Entra Applications page and complete the prompt with an account that can consent on behalf of the organization.

Step 8. Open the setup page

Open MPA Deploy Setup.


Step 9. Enter the credentials

In the Entra Application group:

  • Client ID: paste the Application (client) ID
  • Client Secret: paste the secret value from Step 3

Step 10. Test the connection

Select Test Connection.

The app requests a token, calls the installed apps endpoint on the target environment, and reports how many extensions came back. A number means authentication, permissions, and environment routing are all correct.

Step 11. Assign the permission set to users

Assign MPA Deploy Admin to the people who should be able to deploy. It is a standalone permission set and is not included in any Microsoft permission set, so nobody gets it by accident.

Step 12. Deploy a package

Open Installed Extensions (Admin Center) and Upload Extensions action.
Select Select Package, choose an .app file, decide whether to install now or wait for the update window, and select Deploy.



Step 13. Check the Installation Status

Open Installation Status action. Track the status to watch the install complete.

 



The source is on GitHub. Try it in a sandbox, tell me where it breaks, and open an issue if something is missing or wrong. Feature suggestions are just as welcome as bug reports.

https://github.com/pmohanakrishna/PTE-Extension-Management