Open any Business Central extension folder and the first file you see is app.json. It is the manifest: what the compiler, the platform, AppSource and the admin centre read to know what this app is, who published it, what version it is, and what it needs. Most of it is set by the template and never looked at again. A few properties are the difference between an app that upgrades cleanly for years and one that will not install. This is the full list, from Microsoft's reference, with what each one is for.
A typical per-tenant app.json
{
"id": "7f3a2c1e-9b44-4e0a-8d6b-2c5f1a9e0d11",
"name": "Contoso Customisations",
"publisher": "Contoso Ltd",
"version": "29.1.0.0",
"brief": "Contoso-specific pricing, labels and courier integration",
"description": "Per-tenant extension holding all Contoso bespoke functionality.",
"privacyStatement": "",
"EULA": "",
"help": "",
"url": "https://contoso.example",
"logo": "logo.png",
"dependencies": [
{ "id": "2c0d7f8a-1b3e-4c5d-9e6f-0a1b2c3d4e5f", "name": "Courier Connector", "publisher": "Some ISV", "version": "5.0.0.0" }
],
"screenshots": [],
"platform": "29.0.0.0",
"application": "29.0.0.0",
"idRanges": [ { "from": 50100, "to": 50299 } ],
"resourceExposurePolicy": {
"allowDebugging": true,
"allowDownloadingSource": true,
"includeSourceInSymbolFile": true,
"applyToDevExtension": true
},
"runtime": "18.0",
"features": [ "NoImplicitWith", "TranslationFile", "TranslationsWithNamespaces" ],
"target": "Cloud",
"applicationInsightsConnectionString": "InstrumentationKey=...;IngestionEndpoint=..."
}
Identity and description
| Property | Required | What it does |
id | Yes | A GUID, generated once when the project is created. The platform binds the extension's tables and fields to it at runtime (the database columns are suffixed with it), so changing it means the old data is no longer the app's data. Never change it. |
name | Yes | The extension's name as shown in Extension Management and AppSource. Can be changed (for rebranding or after an acquisition) with a version increment; dependent apps then have to refresh symbols and recompile. AppSource apps cannot change it after publication (AppSourceCop AS0096). |
publisher | Yes | Who published it. Same rules as name: changeable on a per-tenant extension, fixed on AppSource (AS0097). For a per-tenant extension it should be the company that owns the IP (see who owns the code). |
version | Yes | Four parts, major.minor.build.revision. Every install must be higher than what is installed; the platform refuses a downgrade. Let the pipeline own it. |
brief, description | Marketplace only | Short and long descriptions shown in Extension Management and AppSource. Fill them in for per-tenant apps too; a blank description helps nobody in three years. |
privacyStatement, EULA, help, url | Marketplace only | URLs: privacy policy, licence terms, online help, and the extension's website (shown as "Website" on the Extension Management page). |
logo, screenshots | Marketplace only | Relative paths to the logo and screenshots packaged with the app. |
Platform, dependencies and ranges
| Property | Required | What it does |
platform | If system objects are referenced | Minimum platform (runtime and system layer) version, for example 29.0.0.0. The app installs on that version and later. Corresponds to Microsoft_System_*.app. |
application | If the base or system application is referenced (Marketplace: yes) | Minimum System and Base Application version the code was compiled against, for example 29.1.0.0. Corresponds to Microsoft_Application_*.app. On-premise, a code-customised base application can be referenced here, keeping "name": "Application". |
dependencies | No | Other apps this one references: each with id, name, publisher and the minimum version (the latest satisfying version is used). System and base applications are not listed here; application covers them. Keep it short: every dependency ties your release to theirs. |
idRanges (or idRange) | Yes (one or the other) | The object and field ID ranges the app may use, for example [{"from": 50100, "to": 50299}]. Objects outside the range are a compile error; overlapping ranges are a compile error. Per-tenant: 50000 to 99999; AppSource: a licensed range. Can be changed later; what cannot be changed without consequences is the number of a table or field that has data. |
runtime | No | The AL runtime version targeted: 13.0 (2024 wave 1), 14.0 (2024 wave 2), 15.0 (2025 wave 1), 16.0 (2025 wave 2), 17.0 (2026 wave 1), 18.0 (2026 wave 2). The app can publish to a server on the same or higher runtime. If omitted the compiler uses the highest it knows. |
target | No | Cloud (default) or OnPrem. OnPrem unlocks restricted APIs and .NET interop and requires the server to be set to OnPrem too. Build for Cloud unless you have a specific reason. |
test | No | Version of the test framework; only for version 14 and earlier. Ignore for anything current. |
alternateIds | No | IDs of apps this one replaces. Only for apps that replace the base application. |
Source exposure, telemetry and secrets
| Property | Required | What it does |
resourceExposurePolicy | No | Object with four booleans, all defaulting to false: allowDebugging (others can debug into the app's code), allowDownloadingSource (the source can be downloaded from Extension Management), includeSourceInSymbolFile (source ships in the symbols, so dependants can see it), and applyToDevExtension (apply the policy to Dev-scope publishing too). For a per-tenant extension the customer owns, set all four true. ISVs protecting a product leave them false. Replaces the old showMyCode. |
applicationInsightsConnectionString | Recommended for Marketplace | The Application Insights connection string the app's telemetry is sent to. For a per-tenant extension, point it at the customer's resource. |
keyVaultUrls | No | Azure Key Vaults the app may read secrets from (all in the same Entra tenant). How ISVs store API keys without putting them in the database. |
resourceFolders, publicResourceFolders | No | Folders of files packaged with the app: private (only this app reads them) or public (other apps can read them through NavApp resource methods). |
Compiler behaviour
| Property | Required | What it does |
features | No | Compiler flags: NoImplicitWith (always on now), TranslationFile (generate a Translations folder with an XLIFF of all labels), GenerateCaptions (make captions for objects that have none; needs TranslationFile), GenerateLockedTranslations (include locked labels in the XLIFF), TranslationsWithNamespaces (include namespaces in translation IDs; runtime 18 or later). |
preprocessorSymbols | No | Symbols for #if directives, for example ["DEBUG"]. Use sparingly. |
suppressWarnings | No | Warning IDs to hide in the output window, for example ["AL0458"]. Better to fix them; use this for the ones you have consciously accepted. |
internalsVisibleTo | No | Other apps (id, name, publisher) that may call this app's Access = Internal objects. Triggers cop warnings online; internal is not a security boundary. |
propagateDependencies | No | Boolean. If true, this app's dependencies are visible to apps that depend on it (all of them; you cannot pick). Used by foundation apps. |
contextSensitiveHelpUrl | Marketplace only | Where the client's Help links go for this app's pages. Use /{0}/ in the URL for the locale if you support several. |
helpBaseUrl, supportedLocales | No | Replace the default help base URL for given locales; list of locales (first is default) substituted into help URLs. Meant for localisation apps. |
source | No | Object with repositoryUrl and commit: which repository and commit this build came from. AL-Go fills it in; it is how you know what code is running in production. |
build | No | Object with by (the build system, for example AL-Go for GitHub) and url (the build run). Same purpose. |
The properties you cannot change later
id: never.
publisher and name: these can be changed on a per-tenant extension. Since 2021 release wave 2 the platform identifies an app by its id, so a new version with a new name or publisher installs over the old one and keeps its data. It is still worth getting the publisher right at the start, because it is what users and future partners see, and because AppSource apps cannot change either: AppSourceCop rules AS0096 (name) and AS0097 (publisher) block it against the published baseline.
idRanges: can be changed freely (added to, widened, replaced) as long as every object still sits inside the declared ranges. What has consequences is renumbering the objects themselves. Pages, codeunits, reports, queries and enums can be renumbered; you will break references by ID, user personalisations and permission sets that name the old number, but no data is lost. A table, or a table-extension field, that has data cannot be renumbered without losing it: the data is stored against the old number, so the new number is a new object and the old data is orphaned unless you migrate it first.
The errors that come from app.json
- "The extension could not be installed because a dependency is missing or at a lower version." The dependencies list, or the version in it.
- "A newer version is already installed." Version went backwards.
- "The object ID is outside the allowed range." idRanges.
- "The runtime version is not supported on this platform." runtime higher than the target environment.
- "Cannot access internal/OnPrem object from a Cloud extension." target.
- Two extensions with the same name and publisher. Usually the in-client Designer creating a second app with a similar identity; consolidate.
Pair this with launch.json, the other file in every AL project, which does something completely different. Microsoft's reference for everything above is the "JSON files" page in the Business Central developer documentation.
Talk to usWant this done in your Business Central?
More from the blog