Writing your first AL extension
By Emil Björk · Microsoft business apps consultant, Gothenburg
A first-walkthrough of building, publishing, and running a basic AL extension for Business Central — toolchain, project structure, and deployment.
On this page (11)
This is the developer first-walkthrough. Setting up the environment is the most fiddly part; the AL itself is straightforward.
Toolchain
Install Visual Studio Code and add Microsoft's AL Language extension from the marketplace. That installs the AL compiler, language services, and debugger. You'll also want Git (for version control) and either a Docker environment for on-premise development or — far more common — a Business Central sandbox in your Microsoft 365 tenant for cloud development.
Sandbox
From the Business Central admin centre, create a new sandbox environment. Note its name, tenant ID, and the sandbox URL. The sandbox is free, fresh out of the box, and has demo data you can throw away.
Project
In VS Code, run AL: Go! from the command palette. Pick Microsoft cloud sandbox. Sign in with your tenant admin. The command scaffolds a new AL project folder with app.json (manifest), launch.json (debug configuration), and a sample HelloWorld.al page extension that adds a button to the Customer List.
Compile and run
Press F5. VS Code downloads symbols (Microsoft's compiled metadata for the platform and base app), compiles your extension, publishes it to the sandbox, and opens the sandbox URL on the Customer List. Your new action appears in the ribbon; clicking it runs your AL.
Make it your own
Replace the boilerplate with something useful. A common first step: a page extension that adds a custom field to the customer card, plus a table extension that adds the underlying column. Save, F5 again, see it live.
Codeunit and event subscriber
Next, add a codeunit with an event subscriber. Subscribe to OnAfterInsertEvent of the Customer table and write a debug log entry. Press F5; create a customer; see the log fire. You've now written extension behaviour that hooks into Microsoft's code without modifying it — the upgrade-safe pattern.
The app.json fields that actually matter
The manifest looks like boilerplate but three fields deserve attention on day one. idRanges declares which object IDs your extension may use — 50000–99999 is the customer range for per-tenant work; pick a slice and document it, because two extensions claiming the same IDs cannot coexist in one environment. id, publisher, name, version together identify the app; change the id GUID and BC treats it as a different app entirely, orphaning the old one's data. And dependencies lists other apps whose objects you reference — the compiler enforces it, and getting the dependency graph right early saves grief when you later split functionality across apps. While you're in there, set "features": ["NoImplicitWith"] behaviour aside — modern scaffolds handle it — but do turn on the CodeCop / UICop / PerTenantExtensionCop analyzers in settings; they teach you the platform's rules as warnings instead of AppSource rejections.
When F5 doesn't work
The first-session failures are predictable. Symbols won't download: usually a sign-in against the wrong tenant or environment name in launch.json — check environmentName matches the sandbox exactly. "Object ID already in use": another extension (often an abandoned earlier attempt) claims the range — uninstall it from Extension Management or change your range. Publish succeeds but nothing visible: you're in a different company, or the page extension targets a page the role centre doesn't surface — navigate to the page directly by search. None of these mean anything is broken; they're the toll for the first afternoon.
Source control
Initialise a Git repo. Commit early. The app.json and source files are everything you need — there is no binary state worth keeping outside Git, and the .alpackages symbol cache belongs in .gitignore.
From Hello World to something deployable
The sandbox F5 loop publishes a development-scope extension, which is for exactly this: iterating. Getting code into a real environment is a different act — a per-tenant extension uploaded through Extension Management (or better, deployed by a pipeline), with a version number that increments and a schema BC will hold you to. Two habits to build before your first real deployment: never delete a field or object that has shipped (mark it obsolete instead — schema removal breaks upgrade), and add a permission set object to the app so admins can grant access without hand-crafting permissions. The wider structure — dependencies, affixes, when to split apps — is covered in AL extensions architecture.
Next steps
Read Microsoft Learn's AL language reference (and how AL and its runtime got here for the wave-by-wave context); experiment with permission sets, translations, and dependencies; write a first test codeunit against the AL test framework; and look at the AL-Go for GitHub template, which turns the project into a build-and-deploy pipeline — the path described in BC CI/CD with AL-Go. Event subscribers, the pattern from this walkthrough, scale into the platform's whole integration model; AL events and integration patterns is the natural second read. The first time an extension needs an API key or a secret, isolated storage and secrets in AL is the reference for doing it safely.
Frequently asked questions
What do I need to start AL development?
- Visual Studio Code with Microsoft's AL Language extension, Git, and a Business Central sandbox created from the admin centre. Run AL: Go! from the command palette to scaffold the project, then F5 downloads symbols, compiles, publishes, and opens the sandbox.
Which app.json fields matter on day one?
- idRanges (50000–99999 is the per-tenant customer range; document your slice), the id GUID with publisher, name, and version (changing the id makes BC treat it as a different app), and dependencies. Turn on the CodeCop, UICop, and PerTenantExtensionCop analyzers in settings.
Why does F5 fail on the first afternoon?
- Symbols will not download when the environmentName in launch.json does not match the sandbox; an Object ID already in use error means another extension claims the range; a successful publish with nothing visible usually means the wrong company or a page the role centre does not surface.
What habits should be in place before the first real deployment?
- Never delete a shipped field or object — mark it obsolete, because schema removal breaks upgrade — and add a permission set object so admins can grant access. Keep .alpackages out of Git and move to a per-tenant extension deployed by a pipeline rather than the F5 development-scope loop.
Further reading
Related guides
- Business Central CI/CD with AL-GoHow AL-Go for GitHub turns an AL extension repo into a build-test-deploy pipeline — secrets, environments, and continuous delivery.
- The AL test frameworkWriting automated tests in AL — test codeunits, test runners, TestPage, mocking, test isolation, and CI with AL-Go.
- The Business Central API and OData servicesHow external systems talk to Business Central — the v2.0 REST API, OData web services, bound actions, and call limits.
- AL events and integration patternsHow AL events let extensions hook into Business Central — business events, integration events, subscriber patterns, and what to avoid.
- AL extension architectureHow AL extensions are structured in Business Central — objects, namespaces, app.json, dependencies, and the runtime model.
Browse every guide in Business Central or just AL & development.
Was this helpful?
Signals which guides land and which need work. No account, no comment box — corrections go through the contact page.
Spot something wrong or want a topic covered? Send a correction or a topic request — both are welcome.