Contribution Guidelines — Bestway Business Central Extensions
Version: 1.0 Effective Date: March 2026 Prepared by: Phil McCaffrey, ERP Specialist Organization: Bestway (USA) Inc.
Purpose
This document defines the standards that all developers — internal and external — must follow when building, modifying, or delivering Business Central extensions for Bestway USA. These standards apply to AL extension code and to any supporting scripts (Python tooling, integration tests) delivered alongside extensions. These standards exist to ensure that every extension in our environment is testable, documented, maintainable, and production-ready before it ships.
Going forward, Bestway will enforce these guidelines as acceptance criteria for all extension deliveries. Work that does not meet these standards will be returned for remediation before UAT can proceed.
1. Extension Configuration
1.1. app.json Requirements
Every extension's app.json must include:
| Field | Required | Details |
|---|---|---|
name | Yes | Short, descriptive extension name |
publisher | Yes | Publisher organization |
version | Yes | Four-part version (major.minor.patch.build) — must match all documentation |
idRanges | Yes | Reserved, non-overlapping ID ranges for all objects |
dependencies | Yes | Complete list of extension dependencies with correct app IDs and versions |
features | Yes | Must include "NoImplicitWith" |
applicationInsightsConnectionString | Yes | Bestway's shared Azure Application Insights connection string (provided upon engagement) |
resourceExposurePolicy | Yes | Must set allowDebugging, allowDownloadingSource, and includeSourceInSymbolFile to true |
1.2. Object ID Ranges
Each extension has a reserved, non-overlapping range of IDs defined in idRanges. All new objects — tables, pages, codeunits, enums, permission sets — must use IDs within the assigned range. Never reuse IDs across extensions. If you are running low on available IDs, request an expanded range before proceeding.
1.3. Telemetry
Every extension must include extension-level telemetry. The Application Insights connection string will be provided by Bestway at the start of the engagement. This sends extension-emitted events to Bestway's centralized monitoring — it is not optional.
1.4. Version Numbering
Use four-part versioning: major.minor.patch.build. The version in app.json must match the version referenced in all documentation (CHANGELOG, CHANGE documents, test plans). Bump the version for every deliverable — there should never be a delivery without a corresponding version increment.
2. Repository Structure
2.1. Folder Organization
Extensions must organize AL objects by type in dedicated subfolders under src/. Use PascalCase, singular names for subfolder names:
ExtensionFolder/
├── docs/
│ ├── CHANGELOG.md
│ └── CHANGE-v{version}.md
├── src/
│ ├── Codeunit/
│ ├── Table/
│ ├── TableExt/
│ ├── Page/
│ ├── PageExt/
│ ├── Enum/
│ ├── EnumExt/
│ ├── PermissionSet/
│ ├── Report/
│ └── ReportExt/
├── app.json
└── README.md
Note: Some legacy extensions in the repository use different conventions (lowercase pluralized folders, flat structures without
src/, etc.). The structure above applies to all new extensions and major refactors. When modifying an existing extension, follow the convention already established within that extension for consistency.
2.2. File Naming
Files follow the convention {Prefix}{ObjectName}.{ObjectType}.al. Examples:
PhoneIntegration.Codeunit.alNextivaConfig.Table.alNextivaSetupPage.Page.al
Maintain consistent casing and naming within an extension. Do not mix conventions.
Note: Some legacy extensions use alternative naming patterns (e.g.,
CU-60000.PhoneIntegration.alwith object type prefixes and IDs, orPOEmailDispatcher.codeunit.alwith lowercase types). The convention above applies to all new extensions. When modifying an existing extension, follow the convention already established within that extension.
2.3. Namespace Declarations
Extensions targeting application: 27.0.0.0 or later should include namespace declarations (e.g., namespace BestwayUSA.bccsmapi;). Extensions targeting earlier versions may omit namespaces.
3. Code Quality Standards
3.1. AL Anti-Patterns — Must Fix
The following patterns will be flagged during code review and must be corrected before acceptance:
| Anti-Pattern | Why It Matters |
|---|---|
Rec.GET(...) without checking the return value | Silent failures cause downstream runtime errors |
| Business logic in page triggers | Untestable; belongs in codeunits |
MODIFY or INSERT inside OnValidate triggers | Causes unexpected side effects and breaks transactions. Legacy code may contain this pattern; new deliveries must not introduce it |
| Hardcoded user-facing text strings | Breaks translation support; must use labels |
FINDSET / FINDFIRST without SETRANGE on multi-record tables | Full table scans cause performance degradation and lock contention. Single-record setup tables accessed via Get() or unfiltered FindFirst() are exempt |
| Hardcoded record IDs, enum values, or magic numbers | Unreadable and fragile; use named constants or enums |
COMMIT calls without documented justification | Breaks transactional integrity; use only after completing a logical unit of work. When Commit() is used to break long-running transactions (see Section 4.2), document the reason and partial-completion behavior in a code comment |
REPEAT loops without proper exit conditions | Risk of infinite loops on record sets |
3.2. AL Anti-Patterns — Should Fix
These patterns are flagged during review and should be corrected unless there is a documented justification:
| Anti-Pattern | Why It Matters |
|---|---|
CALCFIELDS without prior filtering | Triggers SQL aggregates on unfiltered data — wasteful |
Using RecordRef / FieldRef when typed access is available | Slower and less readable than direct field references |
| Overly complex triggers (20+ lines) | Hard to test, debug, and maintain — delegate to codeunits |
| Event subscribers without filtering | Fire on every record regardless of context — expensive at scale |
| Not using temporary records for read-only operations | Risk of unintended database writes |
3.3. Architecture Expectations
- Business logic belongs in codeunits, not page or report triggers. This is not a suggestion — it is a requirement for testability. Page triggers should call codeunit procedures, not implement logic directly.
- Avoid deep nesting. More than 3–4 levels of indentation signals logic that should be refactored with early returns or extracted into helper procedures.
- No dead code. Remove unreachable branches, unused variables, and commented-out blocks before delivery.
- No hardcoded credentials or environment-specific values in source code. Configuration belongs in setup tables or environment variables.
4. Performance Standards
Performance is reviewed as part of the acceptance process. The following areas are evaluated for every delivery:
4.1. Database Access
SetLoadFieldscorrectness —SetLoadFieldsis "sticky" on a record variable. It must only be applied to variables used for read-only queries. If the same variable is later used forInsert,Modify, orGet, partial field loading can silently corrupt data. Use separate variables for lookup and write operations.FindFirstvs.FindSet— UseFindFirstwhen you need one record (existence checks, lookups). UseFindSetwhen iterating withrepeat...until Next() = 0. UsingFindSetfor a single record, orFindFirstinside a loop, are both anti-patterns.- Redundant database calls — Duplicate
FindFirst(),Get(), orFindSet()calls on the same variable with unchanged filters will be flagged. Each call is a database round-trip.
4.2. Locking and Concurrency
LockTable()scope — Table-level locks on high-traffic tables (Customer, Item, Sales Header) are treated as critical risks and must be justified. Evaluate whether record-level locking is sufficient.- Transaction duration — Long-running transactions that include HTTP calls, blob uploads, or other I/O hold database locks for their entire duration. Look for opportunities to break work into micro-transactions with
Commit()per iteration. When usingCommit()to break transactions, document the reason and the partial-completion behavior so reviewers can evaluate the tradeoff (partial batches persist if interrupted, but releasing locks prevents blocking other users). - Lock contention — If an API page and a Job Queue codeunit both operate on the same tables, neither should hold locks that block the other during normal operation.
4.3. Index and Key Usage
- Secondary keys must match filter patterns — When
SetRangeorSetFiltertargets non-primary-key fields, a secondary key covering those fields should exist. Missing indexes cause full table scans that degrade as record counts grow. - Index write overhead — Every secondary key adds overhead to
Insert,Modify, andDelete. The access pattern must justify the index.
5. Documentation Requirements
5.1. README.md
Every extension must include a README.md in its root folder. This serves both developers and non-technical stakeholders.
Required sections:
- Overview — What does the extension do and why does Bestway need it?
- Features — Key capabilities
- Prerequisites — What must be set up before installation?
- Installation and Configuration — Step-by-step setup, including any setup tables or pages
- Usage — How do end users interact with the extension?
- Dependencies — All extension dependencies
- Known Limitations — Edge cases, unsupported scenarios, or accepted workarounds
Writing standard: Assume readers may not be AL developers. Explain concepts without unnecessary jargon. Use numbered steps for procedures. Include screenshots for UI changes where helpful.
5.2. Change Documentation (Two-Tier)
Every extension maintains two tiers of change documentation in docs/:
Tier 1 — docs/CHANGELOG.md (summary index)
- One entry per released version, newest at top
- Version numbers match
app.jsonexactly - Dates in ISO 8601 format (YYYY-MM-DD)
- Changes categorized as: Added, Changed, Fixed, Removed, Deprecated, Migration Notes
- Each entry links to the detailed CHANGE document
Tier 2 — docs/CHANGE-v{version}.md (detailed narrative)
Each version gets a detailed change document covering:
- Background — Context and rationale for the changes
- Summary of Changes — Table of all items with severity, category, and description
- Detailed Changes — Per-item sections with file references, before/after behavior, and reasoning
- Object Inventory — All objects in the extension with IDs, types, and status (new/modified/deleted)
- Deployment Notes — Pre/post-deployment steps, verification procedures, and warnings
- Known Limitations — Accepted edge cases with justification
- Testing — Reference to the UAT test plan and coverage status
Rule: Every time the version in app.json is bumped, both CHANGELOG.md and the corresponding CHANGE-v{version}.md must be created or updated. No exceptions.
Reference example: See
Cambay Solutions_BC Dialing Application/docs/CHANGE-v2.3.5.0.mdandCambay Solutions_BC Dialing Application/docs/CHANGELOG.mdin the repository for a working example of the two-tier format.
5.3. Self-Documenting Code
- Docstrings and type hints on all public procedures
- Comments only where logic is not self-evident — do not comment obvious code
- No tribal knowledge: a developer unfamiliar with the extension should be able to understand the codebase from the source and documentation alone
6. Testing Requirements
6.1. UAT Test Plans
Every extension must have a UAT test plan delivered as a Word document (.docx). The test plan must cover all user-facing behavior and be updated whenever the extension is modified.
Required content:
- Overview — What the extension does and what this plan validates
- Prerequisites — Conditions that must be true before testing begins
- Test Areas — Organized by feature, each containing test cases with:
- Test case ID (e.g., TC-1.1)
- Title
- Steps (numbered: action → expected result)
- Sign-off section — Signature lines for testing and approval
Rule: Test plans are living documents. When the extension changes, the test plan must be updated to cover the new or modified functionality before delivery.
For vendors: Bestway will provide a
.docxtemplate upon engagement. Vendors may submit test plan content in markdown or another structured format; Bestway will convert to the standard template if needed. The key requirement is the content — complete test cases with IDs, steps, and expected results — not the formatting.
6.2. Automated Tests
Extensions with business logic must include AL test codeunits.
Requirements:
- Test codeunits use the
[Test]attribute - Each test follows a Given / When / Then structure
- Tests validate expected behavior and edge cases
- All tests pass in the BC Test Tool (page 130401) before delivery
What to test:
- Business logic in helper codeunits
- Validation logic and error conditions
- Integration points (event subscribers, API endpoints)
- Boundary cases (empty values, maximum lengths, concurrent access)
6.3. Test Coverage Expectation
Every user-facing behavior introduced or modified must have at least one test case — either in the UAT test plan, in an automated test codeunit, or both. Untested functionality will be flagged during review.
7. Delivery Expectations
7.1. Source Code
Source code must be delivered and committed to the Bestway repository. This is a standard deliverable, not contingent on any other milestone. Bestway requires source code access to verify implementations, troubleshoot production issues, and maintain extensions after the engagement ends.
Compiled .app packages alone do not constitute a complete delivery.
7.2. What a Complete Delivery Includes
| Deliverable | Description |
|---|---|
| Source code | All .al files committed to the repository |
| Compiled package | The .app file for deployment |
| README.md | Extension documentation per Section 5.1 |
| CHANGELOG.md | Summary changelog per Section 5.2 |
| CHANGE-v{version}.md | Detailed change document per Section 5.2 |
| UAT test plan | Word document per Section 6.1 |
| Test codeunits | Automated tests per Section 6.2 (if business logic exists) |
| app.json | Correctly configured per Section 1.1, with telemetry and version bumped |
7.3. Pre-UAT Gate
UAT will not proceed until all items in Section 7.2 are delivered and the Shipment Readiness Checklist (below) is satisfied. Incomplete deliveries will be returned with a specific list of gaps to address.
7.4. Deployment Communication
Every deployment — to sandbox or production — must be accompanied by a notification to Bestway stakeholders that includes:
- Extension name and version number
- Summary of changes (reference the CHANGELOG)
- Any known limitations or breaking changes
- Verification steps for the target environment
8. Shipment Readiness Checklist
This checklist must be completed before any extension is considered ready to ship. Bestway will verify each item during the acceptance review.

Documentation
- README.md exists and is current
-
docs/CHANGELOG.mdhas a new entry for this version -
docs/CHANGE-v{version}.mdexists with full detail - Code is self-documenting — docstrings on public procedures, no tribal knowledge
Test Plans
- UAT test plan
.docxexists and covers this version's changes - Every user-facing behavior has at least one test case with clear expected results
Testability
- Business logic is in codeunits, not page/report triggers
- Test codeunits exist for extensions with business logic
- All tests pass in the BC Test Tool (page 130401)
Extension Configuration
-
applicationInsightsConnectionStringpresent inapp.json -
resourceExposurePolicypresent with debugging and source access enabled -
app.jsonversion bumped and matches all documentation - All object IDs are within the extension's reserved range
- Dependencies are correct and complete
-
NoImplicitWithenabled in features - Permission sets defined for all tables, codeunits, and pages in the extension — users must be able to operate the extension with the delivered permission set alone (no SUPER required)
Naming and Structure
- Files follow
{Prefix}{ObjectName}.{ObjectType}.alconvention - Objects organized in type-based folders (Codeunit/, Table/, Page/, etc.)
Delivery
- Source code committed to the repository
- Compiled
.apppackage provided - No hardcoded credentials or environment-specific values in source
9. Review Process
Bestway conducts a multi-stage review of all delivered extensions:
- Shipment Readiness Verification — Every item in Section 8 is checked. Missing items are returned as a gap list.
- Code Review — Pattern compliance, dependency safety, edge case handling, security, and over-engineering are evaluated against the standards in this document.
- Performance Review — Database access patterns, locking behavior, index usage, and query optimization are evaluated per Section 4. Findings are classified as Fix (must correct), Fix if (conditional), or Accept (acknowledged, no action).
- Compilation and Test Verification — The extension must compile without errors and all test codeunits must pass. This is objective ground truth.
- Business Logic Review — Bestway stakeholders verify that the implementation satisfies the business requirement as specified in the Solution Design or requirements document.
Extensions that do not pass all five stages will be returned with a detailed remediation list. Bestway is committed to providing clear, specific feedback — not vague rejections — so that gaps can be addressed efficiently.
Appendix A: Anti-Pattern Quick Reference
Always Fix
| Pattern | Language | Risk |
|---|---|---|
| GET without error handling | AL | Runtime errors |
| Business logic in page triggers | AL | Untestable code |
| MODIFY/INSERT in OnValidate | AL | Transaction side effects |
| Hardcoded text strings | AL | Breaks translation |
| FINDSET without SETRANGE (multi-record tables) | AL | Full table scans |
| Hardcoded credentials | AL, Python | Security vulnerability |
Bare except: | Python | Swallowed errors |
| Raw string SQL | Python | SQL injection |
Should Fix
| Pattern | Language | Risk |
|---|---|---|
| COMMIT without documented justification | AL | Broken transactions |
| Complex triggers (20+ lines) | AL | Maintainability |
| RecordRef when typed access exists | AL | Readability, performance |
| Mutable default arguments | Python | Shared state mutation |
| Wildcard imports | Python | Namespace pollution |
| Copy-paste code | Any | Maintenance burden |
| Deep nesting (4+ levels) | Any | Readability |
| Dead code | Any | Confusion, code rot |
Appendix B: Reference Documents
URLs current as of March 2026. Microsoft periodically reorganizes documentation; if a link is broken, search the document title on Microsoft Learn.
| Document | Location / URL |
|---|---|
| This document (markdown source) | docs/CONTRIBUTING.md (repo root) |
| UAT test plan library and usage | tools/README.md (repo root) |
| Keep a Changelog convention | https://keepachangelog.com/en/1.1.0/ |
| Microsoft AL Language documentation | https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/ |
| BC Performance best practices | https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/performance/performance-developer |
| AL coding guidelines for extensions | https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/compliance/apptest-bestpracticesforalcode |
| Azure Application Insights for BC | https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/administration/telemetry-overview |