Embed resources into your app file

With BC25, we can finally package resources with apps directly in the app file. Meaning that we can stop coming up with creative ways to distribute data to make our apps work. Check out the video:

https://youtu.be/jCG3l4mnLns

Starting with Business Central 25.x, you can embed resource files directly into your AL app files. This means you no longer need to host setup data, demo data, or configuration files on external servers like GitHub. In this video, Erik walks through how to configure resource folders, what restrictions apply, and how to read those resources at runtime using the NavApp methods.

The Problem Before Embedded Resources

Before this feature, if your extension needed to populate demo data, setup data, or any kind of initial configuration when first installed, you had limited options:

  • Have the app download the data from an external source (GitHub, a web server, etc.)
  • Provide customers with configuration packages they had to import manually

Both approaches added complexity and external dependencies. With BC 25, those days are gone — you can embed data directly into your app file.

Configuring Resource Folders in app.json

The first step is to declare your resource folders in the app.json file using the new resourceFolders property. This is an array of folder names:

{
  "id": "5a56d2f8-4f67-40b5-8a0e-c686ee4e79dd",
  "name": "ResourcesInApp",
  "publisher": "Default Publisher",
  "version": "1.0.0.0",
  "brief": "",
  "description": "",
  "privacyStatement": "",
  "EULA": "",
  "help": "",
  "url": "",
  "logo": "",
  "dependencies": [],
  "screenshots": [],
  "platform": "1.0.0.0",
  "application": "25.0.0.0",
  "idRanges": [
    {
      "from": 50100,
      "to": 50149
    }
  ],
  "resourceExposurePolicy": {
    "allowDebugging": true,
    "allowDownloadingSource": true,
    "includeSourceInSymbolFile": true
  },
  "runtime": "14.2",
  "features": [
    "NoImplicitWith"
  ],
  "resourceFolders": [
    "data1",
    "data2"
  ]
}

These folders must exist in your project directory, or you’ll get a build error. You can create them as standard folders in VS Code.

A quick note on version numbers: To convert between BC versions and AL runtime versions, simply add or subtract 11. BC 25 minus 11 gives you runtime 14. AL runtime 10 plus 11 equals BC 21.

How Resources Are Packaged

An app file is essentially a ZIP file with some extra metadata. When you build your extension, any files in your declared resource folders get bundled into a resources folder inside the app file. You can verify this by opening the .app file with a ZIP tool.

Important: Resource Names Must Be Unique

Erik discovered an important constraint through experimentation. If you place a file called data.txt in both data1 and data2, you’ll get a compilation error:

“There are multiple resources with the name data.”

The folder structure at the root level is flattened — files from all declared resource folders end up in the same root of the resources directory. Resource names must be unique across all resource folders.

However, subfolders within resource folders are preserved. If you create data1/sub1/data3.txt, it will appear as sub1/data3.txt in the resources. Interestingly, a file in a subfolder can share a name with a root-level file since the subfolder path makes it distinct.

Accessing Resources at Runtime

The NavApp codeunit provides several methods for working with embedded resources:

  • NavApp.ListResources() — Returns a List of [Text] containing all resource names (available from runtime 14.1 / BC 25.1)
  • NavApp.GetResourceAsText() — Returns the content of a resource as text, with optional encoding parameter (available from runtime 14.2 / BC 25.2)
  • NavApp.GetResourceAsJson() — Returns resource content directly as JSON
  • NavApp.GetResource() — Returns resource content as an InStream, supporting binary files

It’s worth noting that GetResourceAsJson is a particularly welcome addition. If you’ve ever used the HTTP client to fetch JSON data, you know the pain of having to manually convert the response text into a JSON object. Microsoft can do this much more efficiently in C# than we can in AL, so having it built in is a nice touch.

Example: Listing and Reading All Resources

Here’s the complete example Erik built, which iterates over all embedded resources and displays their content:

namespace DefaultPublisher.ResourcesInApp;

using Microsoft.Sales.Customer;
using System.Utilities;

pageextension 50100 CustomerListExt extends "Customer List"
{
    trigger OnOpenPage();
    var
        Resource: Text;
        InS: InStream;
        data: Text;
    begin
        foreach Resource in NavApp.ListResources() do begin
            //Message('%1', NavApp.GetResourceAsText(Resource));
            NavApp.GetResource(Resource, InS);
            InS.Read(data);
            message(data);
        end;
    end;
}

The code uses the foreach loop with the List of [Text] returned by ListResources(). For each resource, it calls NavApp.GetResource() to get an InStream, then reads the content into a text variable.

Note that the commented-out line shows the simpler GetResourceAsText approach, which works well for text-based resources. The GetResource / InStream approach is more versatile because it also supports binary files — you could embed images, Excel templates, or any other binary data.

A Note on Resource Names with Subfolders

When ListResources() returns file paths for resources in subfolders, it uses forward slashes — for example, sub1/data3.txt. Keep this in mind when referencing specific resources by name.

Resource Limits

Microsoft has set the following restrictions on embedded resources:

  • Maximum size of a single resource file: 16 MB
  • Maximum total size of all resource files: 256 MB
  • Maximum number of resource files: 256 files per extension

These are somewhat arbitrary limits — Microsoft clearly doesn’t want people storing gigabytes of data in app files. An interesting open question is whether embedded resources count against the environment’s storage quota, or if they sit somewhere separate (which would effectively make them “free” storage). Regardless, for most practical use cases — setup data, configuration templates, JSON payloads — these limits are more than sufficient.

Runtime Version Considerations for AppSource

One complication Erik highlights is that the resource features were rolled out incrementally across minor releases:

  • Runtime 14.0 (BC 25.0): resourceFolders in app.json works, but no runtime methods to access resources
  • Runtime 14.1 (BC 25.1): NavApp.ListResources() becomes available
  • Runtime 14.2 (BC 25.2): NavApp.GetResourceAsText() becomes available

For per-tenant extensions (PTE), this is less of a concern — you know exactly which version your customer is running. But for AppSource apps, you need to target a runtime that all your customers can support. In practice, this means you may need to wait until BC 26 comes around before you can fully rely on these features, since you need to maintain compatibility with customers who haven’t yet updated to the latest minor version.

Conclusion

Embedded resources are a long-awaited addition to the AL development experience. They eliminate the need for external file hosting or manual configuration packages when your app needs to ship with initial data — whether that’s JSON configuration, XML templates, text-based setup data, or even binary files like images. While the staggered rollout across 25.0, 25.1, and 25.2 makes immediate AppSource adoption tricky, this is a feature that most AL developers will want to adopt as soon as their minimum supported version allows it.