Convert compiled Microsoft Dynamics 365 Business Central AL packages (.app) into DBML schemas.
Project description
AL-to-DBML
al2dbml turns a compiled Microsoft Dynamics 365 Business Central AL package (.app) into a DBML schema you can paste straight into dbdiagram.io or push to dbdocs.io. It reads SymbolReference.json from the archive (tolerating AL's 40-byte header and Ready-To-Run wrappers), normalises tables, table extensions, enums, and TableRelations, and emits one valid DBML document with Project, Table, Ref, Enum, and TableGroup blocks.
Sample output:
Project "MyApp" {
database_type: 'MSSQL'
Note { '''MyApp 1.0.0.0 by ACME''' }
}
Table "dbo"."Customer" {
"No." varchar(20) [pk, note: '"No."']
"Name" varchar(100)
"Customer Posting Group" varchar(20)
}
Ref { "dbo"."Customer"."Customer Posting Group" > "dbo"."Customer Posting Group"."Code" }
TableGroup "Sales" { "Sales Header" "Sales Line" }
Release notes live in CHANGELOG.md.
Install
Python 3.10+. Runtime depends only on click, pydbml, and PyYAML (for the optional aldoc overlay).
uv tool install al2dbml # recommended — isolated environment, on PATH
pipx install al2dbml # also fine
pip install al2dbml # works inside an activated venv
If you don't have uv:
sudo dnf install uv # Fedora / RHEL
brew install uv # macOS
curl -LsSf https://astral.sh/uv/install.sh | sh # anywhere
Upgrade with uv tool upgrade al2dbml. Verify with al2dbml --version.
Quickstart
al2dbml MyApp.app -o schema.dbml
That's the whole workflow. Drop schema.dbml into https://dbdiagram.io, or push it to dbdocs:
npx -y dbdocs build schema.dbml
Without -o the DBML streams to stdout so you can pipe it anywhere.
CLI options
Run al2dbml --help for the full list. The flags group by purpose:
Output
-o, --output FILE— write DBML toFILEinstead of stdout.--stats— print object counts (tables, enums, refs, groups) to stderr. With no-o, skips the (expensive) render entirely for a fast probe.
Filtering
--include PATTERN— keep only tables matchingPATTERN(fnmatch). Repeatable.--exclude PATTERN— drop tables matchingPATTERN. Applied after--include. Repeatable.
Grouping
Tables are bucketed into TableGroups by the last segment of their AL namespace (so Microsoft.Finance.GeneralLedger → group GeneralLedger); un-namespaced tables fall back to their first whitespace-separated word (Sales Header + Sales Line → Sales).
-g, --group NAME=PATTERN[,PATTERN...]— explicit rule. Repeatable.--group-by namespace|word|none— change the auto-source (defaultnamespace).--no-groups— emit noTableGroupblocks at all.--min-group-size N— drop groups smaller thanNtables (default 2; use1to keep singletons).
Schemas
--table-schema NAME— schema forTableblocks (defaultdbo, matching BC's SQL Server).--enum-schema NAME— schema forEnumblocks (defaultmeta, since BC enums are AL-language metadata, not SQL objects).
dbdocs header
--database-type NAME— value for the DBMLProject { database_type: ... }line (defaultMSSQL). Pass""to omit it. dbdocs.io uses this as the engine label on the rendered schema.
Extensions
--no-merge-extensions— emitTableExtensionsas separate<Target> (Extension)tables instead of merging their fields into the base table.
Rich descriptions
-d, --docs DIR— overlay aldoc-generated YAML field descriptions and table summaries onto the diagram (see next section).
Rich field descriptions (aldoc overlay)
Default column notes come from the AL Caption property — usually just the field name. The actual BC documentation (the "Specifies the customer number..." sentences from Microsoft Learn) lives in the AL ToolTip property and /// <summary> XML doc comments, neither of which the compiled .app keeps.
Microsoft's aldoc tool (bundled with the AL Language VS Code extension) does keep them. Run it once per release, then point al2dbml at the output:
aldoc generate MyApp.app -o ./myapp-docs/ # slow, once per release
al2dbml MyApp.app --docs ./myapp-docs/ -o schema.dbml # fast
You get:
- Each
Tableblock gains aNote { ... }body from the AL/// <summary>of the table. - Each column note leads with the AL
ToolTiptext instead of the bare caption. - Existing
**Condition:**/**References**sections still follow.
Coverage is uneven. Active-document tables (Customer, Item, Sales Header) are richly documented; history and buffer tables often have nothing. Missing entries fall back to the Caption-based note.
Python API
from al2dbml import Diagram, generate, GroupingConfig
# One-shot
dbml = generate("MyApp.app", output_path="schema.dbml", database_type="MSSQL")
# Step-by-step with custom config
diagram = Diagram.from_app(
"MyApp.app",
grouping=GroupingConfig(rules={"Documents": ["Sales*", "Purch*"]}),
includes=["Sales*", "Customer"],
docs=..., # optional AldocDocs from al2dbml.aldoc.load_docs
)
print(diagram.dbml()) # build + render
print(diagram.stats()) # {'tables': N, 'columns': N, ...}
print(diagram.context.tables.keys()) # inspect the live BuildContext
Diagram is a single-shot dataclass: build() is cached, so mutating its fields after the first call has no effect. Construct a new instance to rebuild with different settings.
A second console script, al2dbml-validate FILE.dbml, parse-checks a DBML file via pydbml. Useful as a smoke test in CI, though it doesn't match dbdiagram.io's parser exactly — see Limitations.
Limitations
- Render time scales quadratically with table count inside pydbml. Up to a few hundred tables: sub-second. Microsoft's full Base Application (~1,500 tables) currently takes several minutes. A custom emitter is on the roadmap.
- FlowFields are treated as regular fields; the underlying
CalcFormulais not interpreted. - Obsolete fields are emitted alongside active ones; no filtering by
ObsoleteState. - Multi-field primary keys become multiple
[pk]flags (DBML's single-PK convention) rather than a composite index. - Multi-column secondary keys are not yet emitted as DBML
indexesblocks; only single-column secondary keys surface (as[unique]). - Cross-package references (relations pointing outside the current
.app) degrade to**References** \Target` (cross-package)` notes on the source column, since the target table is absent from the diagram. - Conditional
IF/ELSETableRelations produce oneRefper resolved branch, with the branch's condition recorded as a// when (...)comment on the Ref. Branches whose target is missing degrade to a column note. al2dbml-validateuses pydbml's parser, which is not byte-identical to dbdiagram.io's parser. For an authoritative check matching dbdiagram exactly:dbml2sql FILE --postgresfrom@dbml/cli.
Development
python -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest -q
.venv/bin/ruff check . && .venv/bin/ruff format --check .
Tags matching v* trigger a PyPI Trusted Publisher upload via .github/workflows/publish.yml.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file al2dbml-0.9.0.tar.gz.
File metadata
- Download URL: al2dbml-0.9.0.tar.gz
- Upload date:
- Size: 52.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c0908603f06fea246132a5fef9f9cb2eb82e4cd5daaa2cd5dab0dd53acd3df6
|
|
| MD5 |
8ac2f680433252433441e9f6cb98e517
|
|
| BLAKE2b-256 |
eed68c0c3913628a2d5c6579cb9d49f0cbc10d66a446a19d046064fc0cf67b7a
|
Provenance
The following attestation bundles were made for al2dbml-0.9.0.tar.gz:
Publisher:
publish.yml on mykola-kharchenko/al2dbml
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
al2dbml-0.9.0.tar.gz -
Subject digest:
3c0908603f06fea246132a5fef9f9cb2eb82e4cd5daaa2cd5dab0dd53acd3df6 - Sigstore transparency entry: 1661006688
- Sigstore integration time:
-
Permalink:
mykola-kharchenko/al2dbml@ab242e828f8a10cf774655df38893d5de7c477d9 -
Branch / Tag:
refs/tags/v0.9.0 - Owner: https://github.com/mykola-kharchenko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ab242e828f8a10cf774655df38893d5de7c477d9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file al2dbml-0.9.0-py3-none-any.whl.
File metadata
- Download URL: al2dbml-0.9.0-py3-none-any.whl
- Upload date:
- Size: 38.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0f6308639e218c52d68a17fefd43138ba32b355301ef894264585f62ab9ebb70
|
|
| MD5 |
5306939b0da0852dc17c5a3435157111
|
|
| BLAKE2b-256 |
98bfbd976b933a149470257b6cea7fd2a6dac621b98a9ec3c37cc5aab2502a51
|
Provenance
The following attestation bundles were made for al2dbml-0.9.0-py3-none-any.whl:
Publisher:
publish.yml on mykola-kharchenko/al2dbml
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
al2dbml-0.9.0-py3-none-any.whl -
Subject digest:
0f6308639e218c52d68a17fefd43138ba32b355301ef894264585f62ab9ebb70 - Sigstore transparency entry: 1661007037
- Sigstore integration time:
-
Permalink:
mykola-kharchenko/al2dbml@ab242e828f8a10cf774655df38893d5de7c477d9 -
Branch / Tag:
refs/tags/v0.9.0 - Owner: https://github.com/mykola-kharchenko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ab242e828f8a10cf774655df38893d5de7c477d9 -
Trigger Event:
push
-
Statement type: