Configuration
Config-driven builds use alwasp.json at the repository root. The JSON Schema is published at https://alwasp.dev/schema/alwasp.schema.json.
{
"$schema": "https://alwasp.dev/schema/alwasp.schema.json",
"version": 1,
"defaultTarget": "release"
}
Three layers
| Layer | Purpose |
|---|---|
apps / tests |
Project inventory and source paths |
defaults / profiles |
Build settings and selected projects |
targets |
Ordered profile shortcuts for local and CI workflows |
Minimal config
{
"version": 1,
"defaultTarget": "ci",
"apps": [
{ "id": "Broker", "path": "./Broker" }
],
"tests": [
{ "id": "BrokerTest", "path": "./BrokerTest", "app": "Broker" }
],
"profiles": {
"appsource": {
"include": "apps",
"appSourceCop": true,
"warningPolicy": { "treatWarningsAsErrors": true }
},
"test": {
"include": "tests"
}
},
"targets": {
"ci": ["appsource", "test"]
}
}
Include selectors
profile.include accepts:
| Value | Selects |
|---|---|
"apps" |
Every configured app project |
"tests" |
Every configured test project |
"Broker" |
A single project by id |
["apps", "BrokerTest"] |
Ordered mixed selection |
Per-app compatibility baseline
An app entry can identify the previous compiled package used by config-driven AppSourceCop validation:
{
"apps": [
{
"id": "Broker",
"path": "./Broker",
"compatibility": {
"baseline": "./latest/Broker.app"
}
}
]
}
The baseline path is resolved relative to alwasp.json. By default it is consumed only by alwasp validate compatibility [targetOrProfile]; a profile can opt its normal build into the same validation (see below).
Profile-bound compatibility validation
A profile can make its normal build output the authoritative compatibility-validated artifact,
instead of running validate compatibility as a separate step:
{
"apps": [
{
"id": "Broker",
"path": "./Broker",
"compatibility": { "baseline": "./previous/Broker.app" }
}
],
"profiles": {
"release": {
"include": "apps",
"outFolder": "output/release",
"compatibility": { "enabled": true }
}
}
}
For release, apps declaring compatibility.baseline are compiled once with AppSourceCop bound
to that baseline, and the successful package is collected through the profile’s normal
outFolder/outputSuffix flow. compatibility.enabled implies AppSourceCop only for matched
apps; selected apps without a baseline are compiled normally. Other profiles are unaffected, and
a user-owned AppSourceCop.json is restored byte-for-byte after each compile group. See
Compatibility for the full behavior.
defaults
Compiler settings applied to every profile unless overridden:
| Field | Purpose |
|---|---|
ruleset |
Path to a ruleset file overriding diagnostic severities |
nowarn |
Warning codes to suppress, merged with profile/override nowarn |
warningPolicy.treatWarningsAsErrors |
Fail the build if any non-suppressed warning remains after compile (enforced by ALWasp from parsed diagnostic logs, since altool has no such flag). warnAsError is a legacy alias, used only when warningPolicy is unset at the same level |
errorLog |
Path to write all diagnostics |
features |
Feature flags, merged with profile/override values, passed to the compiler |
defines |
Preprocessor symbols, merged with profile/override values. Config-driven build writes them into each project’s app.json preprocessorSymbols (restoring app.json afterwards) rather than passing a workspace-wide /define — see the note below |
parallel / maxParallelism |
Parallel compilation within a project (default true / 4) |
analyzers |
codeCop, appSourceCop, pteCop, uiCop booleans plus custom analyzer DLL paths. Booleans resolve override → profile → defaults; custom is only definable in defaults |
outFolder |
Default output folder for compiled .app files, used by any profile without its own outFolder |
tooling
Optional overrides for AL compiler tool paths — by default ALWasp downloads tools automatically:
| Field | Purpose |
|---|---|
alcPath |
Path to alc / alc.exe, overrides ALC_PATH |
altoolPath |
Path to altool / altool.exe, overrides AL_PATH |
Profile fields beyond include
| Field | Purpose |
|---|---|
outFolder |
Output folder for this profile’s compiled .app files, overriding defaults.outFolder |
needsProfile |
Profile name(s) whose compiled apps must be staged into the package cache before this profile builds (e.g. a test profile that needs the apps it exercises). The referenced profile must run earlier in the same target and declare an outFolder (its own or defaults.outFolder). Staged apps are removed afterward |
outputSuffix |
Cosmetic suffix appended to each compiled .app file name before the .app extension (e.g. _develop); affects the file name only, never the app identity |
manifest |
Path for the build manifest written after this profile completes; takes precedence over workspace.manifest |
overrides |
Per-project setting overrides, keyed by project id |
Settings merge order
Compiler settings are resolved from the broadest level to the most specific level:
defaults -> profile -> overrides.<projectId>
Use this rule of thumb:
- Put shared settings in
defaults. - Put workflow-specific settings in a
profile. - Put one-off project exceptions in
profile.overrides.<projectId>.
How values combine:
| Setting type | Behavior |
|---|---|
Lists such as defines, features, and nowarn |
Merged from all levels |
| Most booleans and strings | Most specific value wins |
| Analyzer booleans | Resolve override -> profile -> defaults |
custom analyzer DLLs |
Defined only in defaults.analyzers.custom |
Defines and preprocessor symbols
defines are handled differently from most compiler options:
- ALWasp writes the resolved symbols into each project’s
app.jsonpreprocessorSymbols. - The original
app.jsonis restored after the build. - Symbols already present in
app.jsonare preserved and unioned with configured symbols. - A project included by more than one selected profile resolves its symbols once; the first including profile wins.
This avoids passing a workspace-wide /define value to altool. Projects that differ only by defines can stay in the same compile group, which reduces the number of compiler invocations.
Important sections
These sections are optional. Add only the ones your repository needs.
restore
Controls symbol restore before builds.
Common fields:
enabled: turn automatic restore on or off.mode:Lockedfor stable graph selection, orLatestAllfor floating latest versions.packagesFolder: shared symbol package folder, usually.alpackages.overridesFolder: folder of pre-built.appfiles that replace restored dependencies with the same embedded AppId during config-driven builds; relative toalwasp.json.feeds,nugetConfig,authMode,feedTokenEnv: private feed and authentication settings.country: optional Business Central localization code for Microsoft symbols.
Set country to a code such as DE to prefer matching localized Microsoft symbols, including explicit test-library dependencies. Restore falls back to the unlocalized package when a matching localization does not exist. Omit country or use W1 to prefer unlocalized/W1 packages.
workspace
Controls how config-driven workspace compilation runs.
Common fields:
root: workspace root path.maxCpuCount: passed toaltool workspace compile.logDirectory: wherealtoollogs are written.manifest: default config-driven build manifest path.diagnostics: parse and display compiler diagnostics from logs.continueOnError: continue with later groups/profiles after a failed group.bcVersion: fallback Business Central version for Application Insightsautomode.sourceUrl/sourceCommit: optional manifest metadata.
versioning
Calculates effective app versions before build or during alwasp version apply.
Key choices:
enabled: turn version calculation on or off.source:appJson,nuget, orexplicit.explicitVersion: required whensourceisexplicit.fallbackToAppJson: whensourceisnuget, use the currentapp.jsonversion if no published package is found.releaseType:Release,Preview, orNone.applyTo:allorchangedOnly.includeDependencies: update internal dependency entries to the calculated versions of selected projects; defaults totrue.dependencyUpdateScope:directlyChanged(default) updates a reference only when both the consuming project and the dependency project have direct git changes;allVersionedpropagates every selected calculated version regardless.
Set it at the top level for all profiles, or override individual fields per profile.
Internal dependencies are matched by app GUID, so similarly named external packages are never changed. During build, dependency versions are temporary and the original app.json files are restored. During version apply, both the calculated project version and eligible internal dependency versions are written permanently.
Release and Preview periods
ALWasp uses the Friday closest to the 15th of each month as the release-period switch, and the switch takes effect on that Friday:
| Calculation time | Release targets |
Preview targets |
|---|---|---|
| Before the switch Friday | Previous month | Current month |
| On or after the switch Friday | Current month | Next month |
The resulting version uses the target year and month as its major and minor components. When the current version is already in that target period, ALWasp increments the build component; otherwise, it starts the target period at build 0. December/January transitions roll the year forward or backward as expected, and None leaves the current version unchanged.
For example, in August 2026 the 15th is a Saturday, so the closest Friday is August 14. A Release calculation on August 13 still targets July; on August 14 it targets August. A Preview calculation switches from August to September on the same date.
The former Hotfix release type has been removed. Repeated Release calculations within the active release period already increment the build component and cover that workflow.
resourceExposurePolicy
Temporarily writes Business Central source/debugging exposure fields into app.json before compilation.
Supported fields:
allowDebuggingallowDownloadingSourceincludeSourceInSymbolFile
Unset fields are left untouched, and the original app.json is restored after build. The removed showMyCode and enableDebugging config fields are not supported.
applicationInsights
Temporarily writes Application Insights settings into app.json.
Key fields:
enabled: turn injection on or off.source:literal,environment,environmentByProject, orliteralByProject.mode:auto,connectionString, orinstrumentationKey.value: used withsource: literal.environmentVariable: used withsource: environment.environmentVariables: project id to environment variable map forsource: environmentByProject.values: project id to literal value map forsource: literalByProject. UnlikeenvironmentVariables, there is no environment-variable indirection — the mapped string is written toapp.jsonas-is, for apps with their own fixed, permanently-assigned Application Insights resource.
mode: auto chooses between applicationInsightsConnectionString and applicationInsightsKey from the app’s runtime, falling back to workspace.bcVersion. Secret values are never written to the manifest or logs.
changeDetection
Maps local git diffs to configured projects. Used by versioning.applyTo: changedOnly, by
validate translations to check only changed apps, and surfaced in
console output and the build manifest.
Common fields:
mode:gitto enable for every build/version-apply/translation-validation run, ornoneto enable only when--changed-sinceis passed.base:latest,latest:<glob>,latest-merge:<text>, or an explicit tag, branch, or commit.head: defaults toHEAD.includeDependents: whentrue(the default), also marks transitive dependents as changed.
latest-merge:<text> selects the nearest merge commit on head’s first-parent history whose commit message contains the supplied text. This is useful when a release merge, rather than its earlier release tag, should begin the next change-detection cycle. The local checkout must contain enough history to reach the merge.
ALWasp discovers the git repository from each selected project’s own folder — the folder holding
alwasp.json does not itself need to be a checkout — and runs git diff --name-only base..head
independently per discovered repository. This makes change detection work correctly across a
config that spans multiple git repositories, not only a single repository containing every
project.
changeDetection.includeDependencies remains accepted as a deprecated alias for includeDependents. Use includeDependents in new and updated configurations; it wins if both names are present.
translations
Translation coverage settings used by alwasp validate translations.
Strictness is repository-wide; individual apps may only opt out or narrow their language list.
| Field | Purpose |
|---|---|
enabled |
Set to false to exclude every app from the check. Default true |
languages |
Language tags every checked app must ship, e.g. ["da-DK", "de-DE"]. Also scopes the check: when non-empty, translation files for languages not listed are excluded from validation entirely instead of only failing to satisfy the requirement. Omit or leave empty to check every discovered language |
untranslatedPlaceholders |
Target texts meaning “not translated yet”, e.g. XLIFF Sync’s %EMPTY% marker. Default ["%EMPTY%"]; set to [] to disable |
checkPlaceholders |
Require matching %1/{0}/#-style placeholders between source and target. Default false |
failOn |
none, missingLanguage, missingUnit, untranslated (default), or needsReview |
minCoverage |
Minimum per-language coverage percentage (0-100) |
requireGeneratedFile |
Fail when a project’s generated .g.xlf is absent, instead of only warning. Default false |
An apps[] entry can narrow the shared settings with its own translations block
(enabled and/or a replacement languages list):
{
"translations": {
"languages": ["da-DK", "de-DE"],
"failOn": "untranslated",
"minCoverage": 95
},
"apps": [
{ "id": "Broker", "path": "./Broker" },
{ "id": "Internal", "path": "./Internal", "translations": { "enabled": false } }
]
}
Validate
alwasp config validate
alwasp config validate --config path/to/alwasp.json
Validation reports all errors in one pass:
- Missing/duplicate app or test ids, and test
appreferences to unknown apps - Profiles missing
include, or withinclude/overrideskeys that don’t matchapps,tests, or a known project id outputSuffixvalues that aren’t safe filename fragmentsneedsProfilereferences to an unknown or self profile, or to a profile with nooutFolder(own ordefaults.outFolder) to stage from- Targets with an empty or unknown profile list, and names colliding between
targetsandprofiles defaultTargetnot matching any defined target or profileversioning.explicitVersionnot parseable asMajor.Minor.Build.RevisionapplicationInsights(top level and per-profile): missingsourcewhenenabled, missingenvironmentVariable/environmentVariablesfor the selected source, andenvironmentVariableskeys not matching a known project id- Filesystem checks against the config’s directory: nonexistent app/test paths, missing
app.jsonin a project folder, duplicate physical paths, andoutFolder/packagesFolder/workspace.manifest/workspace.logDirectorylocations that are unsafely nested inside source or output directories