Security change on December 1, 2026: TLS 1.2 will become the minimum standard for 365 business Print Agent. Details and system requirements
365 business development
Concepts

Project configuration (albuild.json)

ALbuild has one developer-facing configuration file: albuild.json. It is committed to the repository and gives developers control over the build without touching pipeline YAML.

There are two scopes, merged with the app folder winning:

  1. Workspace root: albuild.json at the repo root: shared defaults for every app.
  2. App folder: albuild.json next to an app's app.json: per-app overrides (useful in a multi-root workspace such as separate app and test apps).

Project config vs. machine config

This is separate from the machine/runtime settings managed by Get-ALbuildConfig / Set-ALbuildConfig (artifact/package cache folders, retry, telemetry, licensing). Those are per-machine, live machine-wide under C:\ProgramData\ALbuild\config.json, and are not committed.


Settings

KeyTypeDefaultPurpose
countrystringw1Localisation of the BC target version. See BC target selection.
artifactTypestringSandboxSandbox or OnPrem. See BC target selection.
bcVersionstring(empty)Explicit BC version or prefix; empty = newest matching. See BC target selection.
selectstringLatestWhich build to pick when several match: Latest / First / Closest / NextMinor / NextMajor. See select strategies.
testRunnerCodeunitIdint0Test-runner codeunit id; 0 = the isolation default. See Test settings.
testSuitestringDEFAULTName of the test suite to run in the container. See Test settings.
disableTestIsolationboolfalseRun tests without test isolation (uses runner 130451). See Test settings.
feedsarray[]NuGet dependency feeds (URL strings or objects. See Feeds).
universalPackagesarray[]Azure DevOps Universal-feed dependency packages. See Dependency sources.
localPackagesarray['.dependencies']Repo folders of committed .app dependencies. See Dependency sources.
urlPackagesarray[].app dependencies served from a direct download link. See Dependency sources.
excludeProjectsarray[]Folder names to skip in the multi-root build. See Multi-root workspaces.
analyzersarray[]AL code analyzers to run during compile. See Code analysis.
ruleSetstring(empty)Path to a .ruleset applied to those analyzers. See Code analysis.
licenseFilestring(empty)BC license for the build container, as a path or URL. See Build container license.
translationobject(defaults)XLIFF sync and translation-quality settings. See Translation.

When testRunnerCodeunitId is 0, the effective runner is 130450 (test isolation) or, when disableTestIsolation is true, 130451.


BC target selection

country, artifactType, bcVersion and select together decide which Business Central build the app is compiled, published and tested against. They are read by the Get BC Artifact task / Find-BcArtifactUrl (an explicit task input always overrides the config value).

  • country — the localisation of the target build. w1 is the base (worldwide) build; a country code such as de, fr, us, nl, ch, … selects the localized build, which carries that country's regulatory objects (e.g. the German Delivery Reminder / Liefermahnung). Use the country your app ships to, or w1 for a country-neutral app.

  • artifactType — which artifact family to download:

    ValueMeaning
    SandboxThe cloud/sandbox artifact (the default). Matches the SaaS platform; use it for AppSource apps and cloud targets.
    OnPremThe on-premises artifact. Use it when the customer runs BC on-prem (a fixed released build) and for a localized on-prem target — the on-prem artifact carries the full country-specific first-party set.
  • bcVersion — pins the build. Accepts a full version (26.3.36158.36321), a prefix (26, 26.3), or is left empty to take the newest build that matches country/artifactType. Combine with select to control how a prefix (several matching builds) is narrowed to one.

select strategies

When more than one build matches country/artifactType/bcVersion, select decides which one wins:

ValuePicks
Latest (default)The newest matching build. The normal choice — always builds against the most recent patch.
FirstThe oldest matching build. Useful to test against the minimum build a bcVersion prefix still covers.
ClosestThe lowest build ≥ bcVersion, otherwise the newest. bcVersion must be a full version (1.2.3.4). Use it to land on an exact target build (or the next available one) — e.g. to match a customer's installed build.
NextMinorThe next minor insider (pre-release) build. Requires Microsoft Insider access / EULA acceptance; for early compatibility testing only, not for release builds.
NextMajorThe next major insider (pre-release) build. Same insider prerequisite as NextMinor.

Example: on-prem, localized, pinned to one build

A German on-premises target fixed to one released version:

JSONCode
{ "country": "de", "artifactType": "OnPrem", "bcVersion": "26.3.36158.36321", "select": "Latest" }

Test settings

testRunnerCodeunitId, testSuite and disableTestIsolation are read by the Run AL Tests task / Invoke-BcContainerTest, resolved per app.

  • testRunnerCodeunitId — the id of the test-runner codeunit to drive the tests with. 0 (the default) means use the isolation default: 130450 when tests run with test isolation, or 130451 when disableTestIsolation is true. Set a specific id only to use a custom runner.
  • disableTestIsolation — when true, tests run without database test isolation (each test's data changes are not rolled back). Defaults to false (isolation on, runner 130450); true switches the default runner to 130451.
  • testSuite — the name of the test suite to execute inside the container. Defaults to DEFAULT.

Dependency sources

Dependencies are resolved from four kinds of source and all land in each project's .alpackages (used for compilation and then installed into the build container in dependency order):

  1. NuGet feeds (feeds), resolved transitively by app id (Continia, 365bd, SIMOVA/nuget.org, DYCE via a private feed with an apiKeyVariable key, …). See Feeds.
  2. Universal packages (universalPackages), named packages from an Azure DevOps Universal feed (e.g. apps you publish to a Products feed). The build downloads them with the pipeline's System.AccessToken; organization defaults to the collection URI and project to the running project.
  3. Local packages (localPackages), repository folders of committed .app files, for ISVs with no public feed. Defaults to .dependencies; paths are relative to the repo root.
  4. URL packages (urlPackages), .app files served from a direct download link (an ISV that publishes a link rather than a feed, e.g. bms GmbH's b-jira). Each entry is a URL string or { url, tokenEnv?, header? }.

Source precedence: localPackages/urlPackages → feeds → universalPackages. An app provided locally or by URL is authoritative — it is pinned and used even when a feed offers a newer version. Feeds take precedence over universal packages (a universal copy is only a fallback for apps no feed provides). A committed encrypted runtime .app whose app id ALbuild cannot parse on the host is pinned by its {Publisher}_{Name}_{Version}.app file name and installed from localPackages; it is also copied into .alpackages, where both the host and the container compiler resolve against it, so no separate symbol package is needed. See source precedence for the full rules.

JSONCode
{ "feeds": [ "https://pkgs.domain.com/nuget/index.json", { "url": "https://pkgs.dev.azure.com/isv/_packaging/isv-feed/nuget/v3/index.json", "apiKeyVariable": "ISV_FEED_TOKEN" } ], "universalPackages": [ { "feed": "Products", "name": "365_business_api", "version": "*" } ], "localPackages": [ ".dependencies" ] }

Real apps vs. symbol-only

A dependency must come from a source that ships the real app (not symbol-only) if the build installs it to run tests; symbol-only packages can be compiled against but are skipped at install.


Multi-root workspaces

A repository may contain several AL projects side by side (e.g. app, test, migration). With an empty projectFolder the pipeline builds all of them, in dependency order (a project is compiled, published and installed after the in-repo projects it depends on, so app before its test/migration), sharing one container. Each project's compiled .app is copied into its dependents' .alpackages so they compile against it.

Order is derived from each app.json's id/dependencies (see Get-BcProjectBuildOrder). To skip a project, list its folder name in excludeProjects (repo-root albuild.json) and/or the excludeProjects pipeline parameter (comma-separated); the two are merged.

JSONCode
{ "country": "w1", "excludeProjects": [ "migration" ] }

Feeds

Declare the NuGet feeds your dependencies come from once in albuild.json instead of repeating Register-BcFeed in every pipeline. Resolve-BcDependencies reads them automatically (and Register-BcFeed -FromProjectConfig <folder> registers them explicitly if you prefer).

Each entry is either a URL string or an object. Only url is required:

FieldRequiredDefaultPurpose
urlyesThe NuGet v3 service index (.../index.json).
namenoderived from the URLA unique feed name.
kindnoappsapps / runtime / symbols. Rarely needed. See below.
idSchemeno{publisher}.{name}.{id}Package-id template. Rarely needed. See below.
apiKeyno(none)Access key / PAT for a private feed, inline; supports a ${env:NAME} placeholder.
apiKeyVariableno(none)Name of an environment variable holding the access key (keeps it out of the file).

token / tokenEnv are still accepted as aliases for apiKey / apiKeyVariable.

Keep secrets out of the file

Prefer naming an environment variable with apiKeyVariable (or a ${env:NAME} placeholder in apiKey). An inline apiKey is supported for convenience but commits the secret, so don't use it in a tracked albuild.json. If both are given, the inline apiKey wins.

kind and idScheme are almost never needed. The resolver auto-detects runtime/indirect ("metapackage") packages from their NuGet metadata and locates packages by app id via the feed's search service, so it copes with feeds whose ids carry a .runtime. infix or whose publisher/name segments differ from your app.json. Set them only to override an edge case.

JSONCode
{ "country": "de", "feeds": [ "https://pkgs.dev.azure.com/isv-repository/PublicFeeds/_packaging/AppSourceApps/nuget/v3/index.json", "https://pkgs.dev.azure.com/365businessdev/Public/_packaging/MSDyn365BCRuntimeApps/nuget/v3/index.json", "https://api.nuget.org/v3/index.json", { "name": "ISV", "url": "https://pkgs.dev.azure.com/isv/_packaging/isv-feed/nuget/v3/index.json", "apiKeyVariable": "ISV_FEED_TOKEN" } ] }

With the above, the whole dependency step is just:

PowerShellCode
Resolve-BcDependencies -ProjectFolder ./MyApp -TargetPlatform '27.1.41698.51164'

Explicitly passed -Feeds (or feeds already registered with Register-BcFeed) still take part; the config feeds are added, not a replacement.


Code analysis

analyzers names the AL analyzers to run during compilation, and ruleSet points at a .ruleset file applied to them. Setting them in albuild.json means the pipeline and your editor agree on what "clean" means, without repeating the list in YAML.

JSONCode
{ "analyzers": [ "CodeCop", "UICop", "AppSourceCop" ], "ruleSet": "build/albuild.ruleset" }

Analyzers are given by token or short name (CodeCop, UICop, AppSourceCop, PerTenantExtensionCop, with or without the ${...} wrapper), or as an explicit path to an analyzer DLL. An empty list means no analyzers.

Precedence, highest first: an explicit Compile AL App task input, then albuild.json, then the project's .vscode/settings.json (al.codeAnalyzers / al.ruleSetPath). So an existing VS Code setup keeps working, and albuild.json overrides it when you want the build to be stricter.


Build container license

The artifact's built-in Cronus demo license is enough for most builds, but it is restrictive in ways that break real test suites, most commonly its posting-date interval (??11*|??12*|??01*|??02*), which fails any date-dependent test outside those months. Point licenseFile at your own license to replace it. The value is passed to New-BcContainer -LicenseFile and may be a path or a URL.

Keep the license out of source control

albuild.json is committed, so do not put a license path that only exists on your machine, and never commit the license itself. Use a ${env:NAME} placeholder, resolved exactly like feed tokens:

JSONCode
{ "licenseFile": "${env:BC_LICENSE_URL}" }

Translation

The optional translation block controls XLIFF synchronisation and translation quality. Omit it and ALbuild uses sensible defaults: the target languages are taken from the project's existing *.??-??.xlf files, translation memory draws on the project itself plus the BC base application, and the pipeline mode is write locally but check in CI (ALBUILD_CI=1), so a build never silently rewrites translations on the agent.

JSONCode
{ "translation": { "targetLanguages": [ "de-DE", "fr-FR" ], "pipelineMode": "check", // write | check | off "failOnMissing": true, "memory": { "enabled": true, "contextAware": true, "sources": [ { "kind": "self" }, { "kind": "bc-base", "language": "de-DE" }, { "kind": "xlf", "path": "../shared/translations", "priority": 10 } ] } } }
FieldPurpose
targetLanguagesLanguages to maintain. Default: the languages of the existing *.??-??.xlf files.
pipelineModewrite updates the XLIFF files, check fails on drift without writing, off skips the step.
failOnMissingFail when a translation unit has no translation.
memory.enabledUse translation memory to pre-fill units.
memory.contextAwareWeight memory hits by the unit's context, not just its source text.
memory.sourcesOrdered sources. kind is self (this project), bc-base (the Business Central base application) or xlf (a path you provide); priority breaks ties.

Who reads it

  • Get BC Artifact task / Find-BcArtifactUrl, country, artifactType, bcVersion, select (explicit task inputs still win).
  • Run AL Tests task / Invoke-BcContainerTest, testRunnerCodeunitId, testSuite, disableTestIsolation, resolved per app.
  • Compile AL App task / Invoke-BcCompiler, analyzers, ruleSet.
  • Create BC Container task / New-BcContainer, licenseFile.
  • Resolve Dependencies task / Resolve-BcDependencies, feeds, universalPackages, localPackages, urlPackages.
  • The translation commands, the translation block.

In code, Get-ALbuildProjectConfig -AppFolder <app> -WorkspaceRoot <repo-root> returns the merged configuration.


Deprecated pipeline.config

The ALbuild V1 pipeline.config file is still read as a fallback when no albuild.json is present in a folder. Its alTestRunnerId maps to testRunnerCodeunitId. A deprecation warning is logged, rename the file to albuild.json.

Last modified on