
Since Business Central 2024 release wave 2, AL extensions have been able to package resources images, JSON files, templates, anything an extension needs at runtime directly inside the app and access them through code. It’s a genuinely useful capability, and BC partners building anything from item picture galleries to configuration templates have relied on it for over a year now.
It came with one hard boundary: a resource packaged inside your extension was visible only to your extension. If another app on the same tenant needed that same file, the only option was duplicating it inside that app too. Version 29 removes that boundary, and I think it changes more about how multi-app AL solutions get architected than the feature description suggests.
What Actually Changed
An app’s resources now split into two categories, declared directly in app.json: resourceFolders for private resources, exactly as before, and a new publicResourceFolders array for resources the app is willing to expose to other apps.
{ "resourceFolders": [".resources"], "publicResourceFolders": [".publicResources"]}
The existing AL methods for reading resources the overloads without an app ID continue working exactly as they always have, scoped to the current app, and they can still see both private and public resources inside that same app. What’s new is a set of overloads that take an app ID and cross the boundary entirely, but only into resources the target app has explicitly marked public. A private resource is invisible to every other app, full stop, regardless of what ID you pass.
The Part That Actually Matters: Discovery Without Dependencies
The detail worth paying attention to is what this doesn’t require. Reading another app’s public resources doesn’t require adding that app as a dependency in app.json. That’s a meaningful design choice it means resource sharing and code dependency are now two separate concerns, where historically bundling any cross-app capability meant taking on a full dependency relationship whether you needed one or not.
Microsoft also shipped a genuine discovery mechanism alongside direct access. NavApp.ListAppsWithPublicResources takes a pattern and returns every app on the tenant exposing a matching public resource, without the calling app needing to know in advance which extensions might be relevant:
ProviderAppIds := NavApp.ListAppsWithPublicResources('schemas/*.json');foreach ProviderAppId in ProviderAppIds do begin PublicResources := NavApp.ListResources(ProviderAppId, 'schemas/*.json'); foreach ResourceName in PublicResources do ProcessSchema(ProviderAppId, ResourceName, NavApp.GetResourceAsJson(ResourceName, ProviderAppId));end;
That’s a plugin-discovery pattern, not just a file-sharing convenience. An app can find every other installed extension offering a particular kind of resource a schema, a template, a configuration file and consume it directly, without either side needing to know about the other at build time.
Why This Matters for Multi-App Solutions Specifically
Anyone building a BC solution as a family of related apps rather than one monolithic extension has run into the duplication problem this solves directly. A base app and its add-on modules that all need the same set of icons, the same JSON schema definitions, or the same document templates previously had no clean way to share them copy the files into every app, or restructure the whole solution around a shared library dependency just to avoid duplicating a handful of resource files.
Public resource folders make the lighter-weight option available for the first time. A base app can expose its icon set or schema definitions as public resources, and every dependent app can read them directly without either duplicating the files or taking on more architectural coupling than the actual need justifies.
There’s a security-conscious detail worth knowing before relying on this: attempting to read a private resource through another app’s ID returns the exact same not-found error as requesting a resource that doesn’t exist at all. There’s no way to distinguish “this resource exists but is private” from “this resource was never there,” which is a deliberate choice to avoid leaking information about an app’s internal structure to anything probing it from outside.
Finally
A cross-app resource sharing mechanism is easy to file under minor developer convenience, and for a single-app solution, that’s exactly what it is. But for the growing number of BC solutions built as a coordinated family of apps a core platform with ISV add-ons, or a partner’s own suite of complementary extensions this removes a specific, recurring piece of duplication that’s been unavoidable since packaged resources first shipped.
It’s a small feature by page count in the release notes, and a real one by how much it changes the calculus for anyone deciding whether a shared resource justifies a full dependency relationship or can now just be published and discovered instead.


