If app.json is what the extension is, launch.json is where you are publishing and debugging it from Visual Studio Code. It lives in the .vscode folder, it is yours rather than the app's, and it is the file most often found containing something it should not. Every property Microsoft documents, grouped by what you are doing: publishing, attaching, snapshot debugging.
Next to app.json in every AL project is a folder called .vscode, and in it, launch.json. The two are often confused because they sit together and both end in json. They do completely different jobs. app.json describes the app and ships with it. launch.json describes where you, the developer, are publishing and debugging it from Visual Studio Code, and it never leaves your machine, or should not.
launch.json is a standard Visual Studio Code file; every language uses it for "launch configurations". The AL extension reads it to know which Business Central environment to publish to when you press F5 (publish and debug) or Ctrl+F5 (publish without debugging), how to sign in, which page to open, and how the debugger should behave. The file holds a list of configurations and you pick one from the Run and Debug dropdown.
{
"version": "0.2.0",
"configurations": [
{
"name": "Contoso UAT sandbox",
"request": "launch",
"type": "al",
"environmentType": "Sandbox",
"environmentName": "CONTOSO_UAT",
"tenant": "00000000-0000-0000-0000-000000000000",
"server": "https://businesscentral.dynamics.com/",
"startupObjectType": "Page",
"startupObjectId": 22,
"breakOnError": "All",
"breakOnRecordWrite": "None",
"launchBrowser": true,
"enableLongRunningSqlStatements": true,
"longRunningSqlStatementsThreshold": 500,
"enableSqlInformationDebugger": true,
"numberOfSqlStatements": 10,
"schemaUpdateMode": "Synchronize",
"dependencyPublishingOption": "Default"
},
{
"name": "Attach: next web client session (UAT)",
"request": "attach",
"type": "al",
"environmentType": "Sandbox",
"environmentName": "CONTOSO_UAT",
"tenant": "00000000-0000-0000-0000-000000000000",
"breakOnNext": "WebClient",
"breakOnError": "ExcludeTry",
"enableSqlInformationDebugger": true,
"enableLongRunningSqlStatements": true,
"longRunningSqlStatementsThreshold": 500,
"numberOfSqlStatements": 10
}
]
}
Two things in that file that trip people up. tenant for a cloud environment is the customer's Entra tenant: the documentation shows the domain form (contoso.onmicrosoft.com) but the tenant ID GUID works and is unambiguous, which is why we use it; you will find it in the admin centre URL or in Entra. server is not needed for the public cloud (it defaults to https://businesscentral.dynamics.com/) but it is harmless to state it, and it is required for on-premise and for anything that is not the public cloud.
| Property | What it does |
|---|---|
name | What you see in the dropdown. Name it for the customer and the environment so you never publish to the wrong one. |
type | Always al. |
request | launch (publish and run), attach (attach the debugger to a session), or snapshotInitialize (snapshot debugging; see below). |
| Property | What it does |
|---|---|
environmentType | Sandbox or Production for online; OnPrem for a server or container. Cloud attach requires Sandbox. You can state Production here; you cannot publish Dev scope to it online, and you should not be trying to (see Global, PTE or Dev). |
environmentName | The environment's name as shown in the admin centre. Optional for a cloud publish if the tenant has one sandbox; required for attach and snapshot, and in practice always set it. |
tenant | Cloud: the Entra tenant ID (GUID) or domain. On-premise: the tenant name, usually default. Required on-premise. |
server | The server URL. Required on-premise (http://bcserver); optional for the public cloud where it defaults to https://businesscentral.dynamics.com/. |
serverInstance | On-premise only: the service instance name, for example BC. |
port | On-premise only: the development service port (default 7049). |
authentication | On-premise only: UserPassword, Windows or AAD. AAD on-premise needs primaryTenantDomain. |
primaryTenantDomain | On-premise with Entra ID: the organisation's Entra domain URL. |
useVsCodeAuthentication | Default true: sign in through VS Code's built-in Microsoft authentication. False falls back to the older device-code sign-in. |
usePublicURLFromServer | On-premise debug sessions only: true opens the browser at the server's PublicWebBaseURL; false uses the server value in this file. Matters for containers behind a proxy. |
applicationFamily | Embed ISV apps only: the application family on the cloud server. |
| Property | What it does |
|---|---|
startupObjectType / startupObjectId | What the browser opens after publishing: Page (default), Table, Report or Query, and its ID. Set it to the page you are working on. |
startupCompany | Which company to open; needs the two above. |
launchBrowser | Open the client after publishing. The documented default is inconsistent; set it explicitly. |
schemaUpdateMode | What happens to data when your tables change on publish. Synchronize (default) keeps data and refuses destructive changes. Recreate drops and recreates the app's tables, losing their data. ForceSync applies destructive changes and loses the affected data. The last two are sandbox tools; see changing a live extension. |
forceUpgrade | Run the upgrade codeunits even though the version has not changed (needs a changed package ID). For testing upgrade code without bumping versions every time. |
useSystemSessionForDeployment | Run install and upgrade codeunits in a system session, which is how production runs them; you cannot debug them that way. |
dependencyPublishingOption | Whether F5 also publishes workspace apps this one depends on: Default (yes), Ignore (no), Strict (fail if apps depending on this one are not in the workspace). Useful with a foundation app and a customer app open together. |
disableHttpRequestTimeout | Let a publish run indefinitely instead of VS Code timing out. For very large apps or slow servers. |
| Property | What it does |
|---|---|
breakOnError | Stop the debugger on errors: All, ExcludeTry (ignore errors inside Try functions), or None. The old true/false values still work. |
breakOnRecordWrite | Stop on every insert, modify or delete: All, ExcludeTemporary, or None. The way to find out who is writing a table. |
enableSqlInformationDebugger | Show SQL statements in the debugger. |
enableLongRunningSqlStatements / longRunningSqlStatementsThreshold | Flag SQL statements that take longer than the threshold in milliseconds (500 is a sensible start). |
numberOfSqlStatements | How many recent SQL statements the debugger shows. |
Switch the SQL ones on. They are how you find the performance mistakes while you are still writing them.
| Property | What it does |
|---|---|
request | attach. |
breakOnNext | Which kind of session to attach to next, for the user who started the attach: WebClient (the browser client), WebServiceClient (an API, OData or SOAP call), Background (a job queue or other background session), or Agent (an AI agent's session). This is how you debug an integration that only misbehaves when the web shop calls it, or a job that only fails at 2am. |
sessionId | A specific session to attach to, when you know it (from the Sessions page or an error message). |
The attach configuration also takes the connection and debugging properties above. VS Code will generate one for you ("AL: Generated Attach request") the first time you use the attach command; rename it to something you will recognise.
For problems in production you cannot reproduce: record a snapshot of a user's session and replay it in the debugger. request is snapshotInitialize and the configuration adds:
| Property | What it does |
|---|---|
userId | GUID of the user whose next session to record (on-premise, the user name with UserPassword authentication). |
sessionId | A specific session of that user; used with Windows authentication on-premise. |
breakOnNext | As for attach: which kind of session to record. |
executionContext | Debug, Profile or DebugAndProfile. |
snapshotVerbosity | Full (every executed line) or SnapPoint (only lines with snap points; lighter). |
profilingType | Instrumentation (time every frame) or Sampling (sample at intervals; needs executionContext Profile). |
profileSamplingInterval | For sampling: 50, 100 or 150 milliseconds. |
snapshotFileName | Where the snapshot file is saved. |
Since version 21.1 a launch block can be put in a .code-workspace file or in your user settings.json. A project's own launch.json overrides the workspace, which overrides user settings. The practical use: keep your personal sandbox configurations in user settings, and keep the project's launch.json with placeholders only.
Passwords: on-premise UserPassword configurations prompt for credentials and nothing should be added to the file to avoid that. More generally, launch.json contains a specific customer's environment names and tenant ID, and it is specific to one developer. The convention: .vscode/launch.json is in .gitignore, or committed with placeholder values. A repository that changes hands should not carry a dozen customers' tenant details with it.
A working developer's file usually has three or four configurations: the customer's sandbox (the default F5 target), a second sandbox on the next-major preview for compatibility testing, a Docker container for fast iteration, and an attach configuration. If you find yourself editing environmentName by hand to switch, add a configuration instead.
They do not, and that is the point. app.json says what the app is and ships inside the .app file. launch.json says where this developer is sending it today and is never packaged. The one thing that connects them: the platform and application in app.json have to be satisfied by whatever environment launch.json points at, or the publish fails with a version error. Read the app.json piece for that half. Microsoft's reference for everything above is the "Launch JSON file" page in the developer documentation.