Many features need “starter” content: setup templates, default JSON config, RapidStart packages, demo data, or even HTML/email templates. Historically, AL developers ended up stuffing this kind of content into labels, giant Text constants, or helper Codeunits.
With Microsoft Dynamics 365 Business Central 2024 Wave 2, you can package resource files inside your extension and read them at runtime directly from AL. This is great for setup and initialization scenarios because it keeps content in real files (versionable, editable, diff-friendly) instead of in code.
Learn more about the feature here
You can find the full code for the example on GitHub.
How Resource Packaging Works
At a high level:
- You add one or more folders to your extension that contain your resources.
- You declare those folders in your manifest (
app.json) usingresourceFolders. - At runtime, AL reads the resource content using the
NavAppdata type (for example,NavApp.GetResource).
A key point from the release plan: an extension can access only its own resources.
Defining Resource Folders in app.json
To package resources, declare the folders in your project that contain them by adding resourceFolders to app.json. The resourceFolders property contains a list of folders that contain resources that should be packaged as part of the app file. You can specify multiple folders, and each folder can contain subfolders. The resourceFolders should be listed relative to the root of your project.
Example:
{
"id": "00000000-0000-0000-0000-000000000000",
"name": "getresource1",
"publisher": "Default Publisher",
"version": "1.0.0.0",
"platform": "1.0.0.0",
"application": "27.0.0.0",
"runtime": "16.0",
"resourceFolders": ["resources"]
}
Anything under those folders is packaged into the .app.
Resource Limits (Worth Knowing Up Front)
This feature has a few practical limits:
- Any single resource file can be up to 16 MB.
- All resource files together can be up to 256 MB.
- An extension can have up to 256 resource files.
What resources do you have?
With so many resources, you might want to see what’s available. You can use NavApp.ListResources to get a list of all packaged resources, optionally filtered by a path prefix.
Learn more about NavApp.ListResources here.
NavApp.GetResource: The Core Building Block
NavApp.GetResource retrieves a resource that was packaged with the current app and loads it into an InStream.
Syntax (from docs):
NavApp.GetResource(ResourceName: Text, var ResourceStream: InStream [, Encoding: TextEncoding])
ResourceName: the name/path of the resource you want to retrieve.ResourceStream: anInStreamvariable that receives the resource content.Encoding(optional): stream encoding (default is MSDos). In practice, you’ll usually wantTextEncoding::UTF8for JSON and text templates.
Learn more about NavApp.GetResource here.
- Wrong resource name/path: the resource name must match the packaged path. Keep your folder structure simple and consistent.
The Other Methods
If your resource is plain text or JSON, you can often skip the InStream plumbing, although it still works, and use the additional methods instead:
NavApp.GetResourceAsText(Text [, TextEncoding])returns the resource directly asText.NavApp.GetResourceAsJson(Text [, TextEncoding]returns the resource directly as aJsonObject.
If you’re only dealing with text or JSON, those convenience methods can make your code shorter. If you need streaming semantics (or want to control how text is read), NavApp.GetResource is the most general option.
Learn more about NavApp.GetResourceAsText(Text [, TextEncoding]) here.
Learn more about NavApp.GetResourceAsJson(Text [, TextEncoding]) here.
Example 1: Load an Image Resource with NavApp.GetResourceAsJson
A very common real-world scenario is packaging different data configurations as JSON resources, then reading them at runtime.
var
JSONText: Text;
ResourceJSONFileLbl: Label 'json/items.json';
trigger OnOpenPage()
begin
this.JSONText := this.LoadJSON(this.ResourceJSONFileLbl);
end;
local procedure LoadJSON(resource: text): Text
var
ResourceJson: JsonObject;
JSON: Text;
begin
ResourceJson := NavApp.GetResourceAsJson(resource, TextEncoding::UTF8);
ResourceJson.WriteTo(JSON);
exit(JSON);
end;
This keeps your HTML out of AL, while still letting AL “inject” runtime data.
Example 2: Load a Text Resource with NavApp.GetResourceAsText
Another real-world scenario is packaging an email template as a resource, then reading it at runtime. This allows you to provide a default email template that can be easily updated by changing the resource file, without modifying the AL code.
var
SampleText: Text;
ResourceTextFileLbl: Label 'text/sample.txt';
trigger OnOpenPage()
begin
this.SampleText := this.LoadText(this.ResourceTextFileLbl);
end;
local procedure LoadText(resource: text): Text
begin
exit(NavApp.GetResourceAsText(resource, TextEncoding::UTF8));
end;
Example 3: Listing All Packaged Resources
With so many resources, you might want to see and select from the list of resources to present as a selection option.
trigger OnOpenPage()
begin
this.AllResourceNames := this.ResourceTextList('');
this.ImageResourceNames := this.ResourceTextList('images');
end;
local procedure ResourceTextList(filter: Text): Text
var
ResourceList: List of [Text];
ResourceNames: Text;
resourceIndex: Integer;
begin
ResourceList := NavApp.ListResources(filter);
for resourceIndex := 1 to ResourceList.Count() do
ResourceNames := resourceIndex = ResourceList.Count() ? ResourceNames + ' ' + ResourceList.Get(resourceIndex) : ResourceNames + ' ' + ResourceList.Get(resourceIndex) + '\';
exit(ResourceNames);
end;
Wrapping Up
Packaging resources in extensions (and reading them from AL) is one of those quality-of-life features that quickly becomes a standard pattern. It makes setup and initialization cleaner, keeps content in real files, and reduces the temptation to hardcode large templates or JSON blobs in AL. You can also allow for the selection of resources to load at runtime, enabling more dynamic behavior – perfect for loading different email templates based on user preferences or configurations and also demonstration data scenarios.
You can find the full code for the example on GitHub.
Note: The code and information discussed in this article are for informational and demonstration purposes only. This content was written referencing Microsoft Dynamics 365 Business Central 2025 Wave 2 online.


available.

















