This document outlines the coding standards and best practices for AL code in this project. Following these guidelines ensures consistent, maintainable, and high-quality code.
- Quick Reference - Key style rules and patterns
- Common Patterns - Frequently used code structures
- Troubleshooting - Style issue resolution
- Variable Naming Conventions
- Code Formatting Standards
- Object Property Qualification
- String Formatting
- Tooltips
- Text Constants and Localization
- Performance Considerations
- Search Keywords
- Cross-References
-
PascalCase for Object Names: Use PascalCase for all object names (tables, pages, codeunits, etc.)
codeunit 50100 "Sales Order Processor"
-
PascalCase for Variable Names: Use PascalCase for all variable names
var Customer: Record Customer; SalesHeader: Record "Sales Header"; TotalAmount: Decimal;
-
Prefix Temporary Variables: Use prefix 'Temp' for temporary records
var TempSalesLine: Record "Sales Line" temporary;
-
Variable Declaration Order: Variables should be ordered by type in the following sequence:
- Record
- Report
- Codeunit
- XmlPort
- Page
- Query
- Notification
- BigText
- DateFormula
- RecordId
- RecordRef
- FieldRef
- FilterPageBuilder
- Other types (Text, Integer, Decimal, etc.)
-
Use Enums Instead of Options: Always use enums instead of the deprecated option data type
// Preferred: Use enum enum 50100 "Document Status" { Extensible = true; value(0; Open) { Caption = 'Open'; } value(1; "Pending Approval") { Caption = 'Pending Approval'; } value(2; Approved) { Caption = 'Approved'; } value(3; Rejected) { Caption = 'Rejected'; } } // In your table or variable declaration var DocumentStatus: Enum "Document Status"; ```2. **Option Type Exceptions**: The only acceptable uses of option data type are: **Exception 1**: When calling existing procedures that use option parameters ```al // Acceptable when calling standard BC procedures var Direction: Option Forward,Backward; begin // Calling a standard procedure that expects option parameter Customer.Next(Direction::Forward); end;
Exception 2: When subscribing to events that use option parameters
// Acceptable in event subscribers [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnBeforePostSalesDoc', '', false, false)] local procedure OnBeforePostSalesDoc(var SalesHeader: Record "Sales Header"; CommitIsSuppressed: Boolean; PreviewMode: Boolean; var HideProgressWindow: Boolean; var IsHandled: Boolean; var DefaultOption: Option " ",Ship,Invoice,"Ship and Invoice") begin // Event handler code here end;
-
Enum Best Practices: When creating enums, follow these guidelines:
enum 50101 "Payment Method" { Extensible = true; // Always make extensible unless there's a specific reason not to value(0; " ") { Caption = ' '; } // Include blank value when appropriate value(1; Cash) { Caption = 'Cash'; } value(2; "Credit Card") { Caption = 'Credit Card'; } value(3; "Bank Transfer") { Caption = 'Bank Transfer'; } value(4; Check) { Caption = 'Check'; } }
-
Converting Options to Enums: When refactoring existing option fields, create corresponding enums
// Old option field (deprecated) // Status: Option " ",Open,"Pending Approval",Approved,Rejected; // New enum approach Status: Enum "Document Status"; // Conversion procedure for data migration procedure ConvertOptionToEnum(OptionValue: Integer): Enum "Document Status" begin case OptionValue of 0: exit("Document Status"::" "); 1: exit("Document Status"::Open); 2: exit("Document Status"::"Pending Approval"); 3: exit("Document Status"::Approved); 4: exit("Document Status"::Rejected); end; end; ```## Code Formatting and Indentation
-
Indentation: Use 4 spaces for indentation (not tabs)
-
Line Length: Keep lines under 120 characters when possible
-
Braces: Place opening braces on the same line as the statement
if Customer.Find() then begin // Code here end;
-
BEGIN..END Usage: Only use BEGIN..END to enclose compound statements (multiple lines)
// Correct if Customer.Find() then Customer.Delete(); // Also correct (for multiple statements) if Customer.Find() then begin Customer.CalcFields("Balance (LCY)"); Customer.Delete(); end;
-
IF-ELSE Structure: Each 'if' keyword should start a new line
if Condition1 then Statement1 else if Condition2 then Statement2 else Statement3;
-
CASE Statement: Use CASE instead of nested IF-THEN-ELSE when comparing the same variable against multiple values
// Instead of this: if Type = Type::Item then ProcessItem() else if Type = Type::Resource then ProcessResource() else ProcessOther(); // Use this: case Type of Type::Item: ProcessItem(); Type::Resource: ProcessResource(); else ProcessOther(); end; ```## Object Property Qualification
-
Use "this" Qualification: Always use "this" to qualify object properties when accessing them from within the same object
// In a table or page method procedure SetStatus(NewStatus: Enum "Status") begin this.Status := NewStatus; this.Modify(); end;
-
Explicit Record References: Always use explicit record references when accessing fields
// Correct Customer.Name := 'CRONUS'; // Incorrect Name := 'CRONUS';
-
Text Constants for String Formatting: Use text constants for string formatting instead of hardcoded strings
// Define at the top of the object var CustomerCreatedMsg: Label 'Customer %1 has been created.'; // Use in code Message(CustomerCreatedMsg, Customer."No.");
-
String Concatenation: Use string formatting instead of concatenation
// Instead of this: Message('Customer ' + Customer."No." + ' has been created.'); // Use this: Message(CustomerCreatedMsg, Customer."No.");
-
Placeholders: Use numbered placeholders (%1, %2, etc.) in labels
ErrorMsg: Label 'Cannot delete %1 %2 because it has %3 entries.'; ```## Error Handling
-
Descriptive Error Messages: Provide clear, actionable error messages
if not Customer.Find() then Error(CustomerNotFoundErr, CustomerNo);
-
Error Constants: Define error messages as constants
CustomerNotFoundErr: Label 'Customer %1 does not exist.';
-
Procedure Comments: Document the purpose of procedures, parameters, and return values
/// <summary> /// Calculates the total amount for a sales document. /// </summary> /// <param name="DocumentType">The type of the sales document.</param> /// <param name="DocumentNo">The number of the sales document.</param> /// <returns>The total amount of the sales document.</returns> procedure CalculateTotalAmount(DocumentType: Enum "Sales Document Type"; DocumentNo: Code[20]): Decimal
-
Code Comments: Add comments to explain complex logic or business rules
- Remove Unused Variables: Delete variables that are declared but not used in the code
// If TempRecord is never used, remove it var Customer: Record Customer; // TempRecord: Record "Temp Record"; // Unused - should be removed
-
Use FindSet() with Repeat-Until: For looping through records
if SalesLine.FindSet() then repeat // Process each record until SalesLine.Next() = 0;
-
Use SetRange/SetFilter Before Find: Limit record sets before processing
Customer.SetRange("Country/Region Code", 'US'); if Customer.FindSet() then
- All fields should have tooltips to provide context and guidance to users
- Use the
Tooltipproperty in AL to define tooltips for fields, actions, and controls - Ensure tooltips are concise and informative, helping users understand the purpose and usage of each field or action
- Avoid overly technical jargon in tooltips; aim for clarity and simplicity
- Use consistent terminology and phrasing across tooltips to maintain a cohesive user experience
- Review and update tooltips regularly to ensure they reflect any changes in functionality or user interface
- Tooltips on fields must start with 'Specifies' to maintain consistency and clarity
- Use text constants or labels for all user-facing strings to support localization
- Define text constants at the beginning of the codeunit or page where they are used
- Use descriptive names for text constants that indicate their purpose
- When using StrSubstNo, always use a text constant or label for the format string
- Format text constant names as: ErrorMsg, ConfirmQst, InfoMsg, etc.
- Example:
var TypeMismatchErr: Label 'Field type mismatch: %1 field cannot be mapped to %2 field.'; begin ErrorMessage := StrSubstNo(TypeMismatchErr, Format(CustomFieldType), Format(TargetFieldType)); end;
By following these guidelines, you'll create more maintainable, readable, and efficient AL code.
- Variable Naming: PascalCase for all variables and objects
- Variable Ordering: Record, Report, Codeunit, XMLPort, Page, Query, then others
- String Formatting: Use StrSubstNo with text constants for all dynamic strings
- Tooltips: Start field tooltips with 'Specifies', use clear and concise language
- Performance: Use SetLoadFields, avoid nested loops, implement proper filtering
// Standard variable declaration order
var
SalesHeader: Record "Sales Header";
Customer: Record Customer;
TempSalesLine: Record "Sales Line" temporary;
ProcessingMsg: Label 'Processing %1...';
TotalAmount: Decimal;Code Structure: Variable naming, PascalCase, object qualification, procedure structure, AL syntax Formatting Standards: Code formatting, indentation, line breaks, string formatting, text constants Performance Patterns: SetLoadFields, bulk operations, filtering, record processing, optimization
Object Development: Page design, field tooltips, user experience, accessibility Extension Standards: AL best practices, code quality, maintainability, AppSource compliance Localization: Text constants, labels, multilingual support, internationalization
Code Quality: Consistent formatting, readable code, maintainable structure, error prevention Style Guidelines: Naming conventions, code organization, documentation, best practices Team Standards: Consistent development, code review patterns, quality assurance
- Naming Conventions:
SharedGuidelines/Standards/naming-conventions.md- Variable and object naming rules - Error Handling:
SharedGuidelines/Standards/error-handling.md- Error message formatting and text constants - Core Principles:
SharedGuidelines/Configuration/core-principles.md- Development foundation
- CoreDevelopment: Implementation of style guidelines in object creation
- TestingValidation: Style standards for test code and validation procedures
- PerformanceOptimization: Performance-focused coding patterns and optimizations
- AppSourcePublishing: Code style compliance for marketplace requirements