Create Custom D365 Business Central API in One Click!
Have you ever been asked to create APIs for dozens (or even hundreds!) of Business Central tables? If you have, you know the pain of manually writing repetitive AL code for each table, defining fields, setting properties, and maintaining consistency across all your API pages.
Well, I recently faced this exact challenge when a client asked me to create APIs for more than 100 Business Central tables. The thought of manually coding each one was daunting—until I had an idea that changed everything.
The Problem: API Development at Scale
Creating custom APIs in Business Central typically involves:
- Writing boilerplate AL code for each API page
- Manually listing all table fields
- Converting field names to camelCase for proper API formatting
- Setting up API metadata (version, publisher, group, etc.)
- Ensuring consistency across all API definitions
For one or two tables, this is manageable. But for 100+ tables? That’s weeks of tedious, error-prone work.
The Solution: An API Generator Page
Instead of writing each API manually, I built a Business Central page that generates the entire API code automatically. This tool runs directly within BC and creates production-ready API page code based on just a few inputs.
How It Works
The generator is incredibly simple to use:
- Enter the Table ID – The tool automatically fetches all field metadata
- Configure API Settings – Set your API version, publisher, and group
- Click “Generate API” – Get complete, copy-paste-ready AL code
The tool leverages Business Central’s built-in Field record and metadata capabilities to dynamically inspect any table and generate properly formatted API code. It saved me 99% of the time I would have spent manually coding!
Key Features
🎯 Automatic Field Generation
The generator loops through all table fields and creates properly formatted API field definitions with:
- camelCase field names (following OData conventions)
- Correct AL syntax with escaped special characters
- Caption and ToolTip properties
🔧 Full Customization
You have complete control over:
- API Version (e.g., v2.0)
- API Publisher (your organization name)
- API Group (categorization)
- Entity names and captions
- CRUD permissions (Insert, Modify, Delete)
- OData key fields
💡 Smart Field Handling
The ToCamelCase function strips out special characters and converts field names to proper API format, handling edge cases like:
- Spaces → removed and capitalized
- Special characters ($, %, #, etc.) → removed
- Consistent lowercase first letter
⚡ Instant Results
Instead of hours of typing, you get production-ready code in seconds that you can immediately embed into your AL project.
Real-World Impact
This tool transformed my development workflow:
- Time Savings: What would have taken weeks took just hours
- Consistency: Every API follows the same pattern and standards
- Fewer Errors: No typos or missed fields
- Easy Maintenance: Need to update an API? Just regenerate it
When to Use This Tool
This API generator is perfect for:
- Bulk API creation projects
- Rapid prototyping and POCs
- Standardizing API patterns across your organization
- Creating integration endpoints quickly
- Learning API development in Business Central
The Complete Code
Below is the full AL code for the API Generator page. You can add this to your Business Central extension project and start generating APIs immediately:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
page 60100 "NWTAPI API Generator" { Caption = 'NWT API Generator'; ApplicationArea = All; UsageCategory = Administration; PageType = Document; AdditionalSearchTerms = 'NWT, API, Generator'; layout { area(Content) { field(TableID; TableID) { ApplicationArea = All; Caption = 'Table ID'; ToolTip = 'Enter the Table ID for which you want to generate the API'; TableRelation = "Table Metadata".ID; trigger OnValidate() var fieldRecord: Record Field; tableName: Text; begin fieldRecord.SetRange(TableNo, TableID); if not fieldRecord.FindSet() then Error('Table ID %1 not found', TableID); SourceTable := fieldRecord.TableName; tableName := fieldRecord.TableName; tableName := tableName.Replace(' ', ''); EntityCaption := Prefix.ToUpper() + ' ' + tableName; EntitySetCaption := Prefix.ToUpper() + ' ' + tableName; EntityName := Prefix + tableName + 'Model'; EntitySetName := Prefix + tableName + 'Models'; end; } field(GeneratedCode; GeneratedCode) { ApplicationArea = All; Caption = 'Generated Code'; ToolTip = 'The generated API code will be displayed here'; MultiLine = true; Editable = true; } field(APIVersion; APIVersion) { ApplicationArea = All; Caption = 'API Version'; ToolTip = 'Specify the API version (e.g., v2.0)'; } field(APIPublisher; APIPublisher) { ApplicationArea = All; Caption = 'API Publisher'; ToolTip = 'Specify the API publisher name'; } field(APIGroup; APIGroup) { ApplicationArea = All; Caption = 'API Group'; ToolTip = 'Specify the API group (e.g., general)'; } field(EntityCaption; EntityCaption) { ApplicationArea = All; Caption = 'Entity Caption'; ToolTip = 'Specify the caption for the API entity'; } field(EntitySetCaption; EntitySetCaption) { ApplicationArea = All; Caption = 'Entity Set Caption'; ToolTip = 'Specify the caption for the API entity set'; } field(EntityName; EntityName) { ApplicationArea = All; Caption = 'Entity Name'; ToolTip = 'Specify the name for the API entity model'; } field(EntitySetName; EntitySetName) { ApplicationArea = All; Caption = 'Entity Set Name'; ToolTip = 'Specify the name for the API entity set models'; } field(SourceTable; SourceTable) { ApplicationArea = All; Caption = 'Source Table'; ToolTip = 'Specify the source table name for the API'; } field(ODataKeyFields; ODataKeyFields) { ApplicationArea = All; Caption = 'OData Key Fields'; ToolTip = 'Specify the OData key fields (e.g., SystemId)'; } field(Extensible; Extensible) { ApplicationArea = All; Caption = 'Extensible'; ToolTip = 'Specify if the API is extensible'; } field(DelayedInsert; DelayedInsert) { ApplicationArea = All; Caption = 'Delayed Insert'; ToolTip = 'Specify if delayed insert is enabled'; } field(InsertAllowed; InsertAllowed) { ApplicationArea = All; Caption = 'Insert Allowed'; ToolTip = 'Specify if insert operations are allowed'; } field(DeleteAllowed; DeleteAllowed) { ApplicationArea = All; Caption = 'Delete Allowed'; ToolTip = 'Specify if delete operations are allowed'; } field(ModifyAllowed; ModifyAllowed) { ApplicationArea = All; Caption = 'Modify Allowed'; ToolTip = 'Specify if modify operations are allowed'; } } } actions { area(Processing) { action(GenerateAPI) { ApplicationArea = All; Caption = 'Generate API'; ToolTip = 'Click to generate the API code for the specified Table ID'; Promoted = true; PromotedCategory = Process; PromotedIsBig = true; Image = GeneralLedger; trigger OnAction() var fieldRecord: Record Field; typeHelperCodeUnit: Codeunit "Type Helper"; begin GeneratedCode := ''; fieldRecord.SetRange(TableNo, TableID); if not fieldRecord.FindSet() then Error('Table ID %1 not found', TableID); // Generate API page header GeneratedCode := 'page 60101 "' + SourceTable + '"' + typeHelperCodeUnit.NewLine() + '{' + typeHelperCodeUnit.NewLine() + 'PageType = API;' + typeHelperCodeUnit.NewLine() + 'APIVersion = ''' + APIVersion + ''';' + typeHelperCodeUnit.NewLine() + 'APIPublisher = ''' + APIPublisher + ''';' + typeHelperCodeUnit.NewLine() + 'APIGroup = ''' + APIGroup + ''';' + typeHelperCodeUnit.NewLine() + typeHelperCodeUnit.NewLine() + 'EntityCaption = ''' + EntityCaption + ''';' + typeHelperCodeUnit.NewLine() + 'EntitySetCaption = ''' + EntitySetCaption + ''';' + typeHelperCodeUnit.NewLine() + 'EntityName = ''' + EntityName + ''';' + typeHelperCodeUnit.NewLine() + 'EntitySetName = ''' + EntitySetName + ''';' + typeHelperCodeUnit.NewLine() + typeHelperCodeUnit.NewLine() + 'SourceTable = "' + SourceTable + '";' + typeHelperCodeUnit.NewLine() + 'ODataKeyFields = ' + ODataKeyFields + ';' + typeHelperCodeUnit.NewLine() + typeHelperCodeUnit.NewLine() + 'Extensible = ' + Extensible + ';' + typeHelperCodeUnit.NewLine() + 'DelayedInsert = ' + DelayedInsert + ';' + typeHelperCodeUnit.NewLine() + 'InsertAllowed = ' + InsertAllowed + ';' + typeHelperCodeUnit.NewLine() + 'DeleteAllowed = ' + DeleteAllowed + ';' + typeHelperCodeUnit.NewLine() + 'ModifyAllowed = ' + ModifyAllowed + ';' + typeHelperCodeUnit.NewLine() + typeHelperCodeUnit.NewLine() + 'layout' + typeHelperCodeUnit.NewLine() + '{' + typeHelperCodeUnit.NewLine() + 'area(Content)' + typeHelperCodeUnit.NewLine() + '{' + typeHelperCodeUnit.NewLine() + 'repeater(General) ' + typeHelperCodeUnit.NewLine() + '{' + typeHelperCodeUnit.NewLine(); // Loop through all fields and generate field definitions repeat // Skip CRM-specific fields if fieldRecord.FieldName <> 'Coupled to CRM' then GeneratedCode := GeneratedCode + 'field(' + ToCamelCase(fieldRecord.FieldName) + '; Rec."' + fieldRecord.FieldName.Replace('$', '') + '")' + typeHelperCodeUnit.CRLFSeparator() + '{' + typeHelperCodeUnit.CRLFSeparator() + 'ApplicationArea = All;' + typeHelperCodeUnit.CRLFSeparator() + 'Caption = ''' + fieldRecord."Field Caption" + ''';' + typeHelperCodeUnit.CRLFSeparator() + 'ToolTip = ''' + fieldRecord."Field Caption" + ''';' + typeHelperCodeUnit.CRLFSeparator() + '}'; until fieldRecord.Next() = 0; // Close the API structure GeneratedCode := GeneratedCode + typeHelperCodeUnit.NewLine() + '}' + typeHelperCodeUnit.NewLine() + '}' + typeHelperCodeUnit.NewLine() + '}' + typeHelperCodeUnit.NewLine() + '}'; Message(GeneratedCode); end; } } } // Convert field names to camelCase format for API compliance procedure ToCamelCase(input: Text): Text var i: Integer; result: Text; capitalizeNext: Boolean; begin result := ''; capitalizeNext := false; // Remove all special characters input := input.Replace('_', ''); input := input.Replace('-', ''); input := input.Replace('.', ''); input := input.Replace('%', ''); input := input.Replace('#', ''); input := input.Replace('@', ''); input := input.Replace('!', ''); input := input.Replace('$', ''); input := input.Replace('&', ''); input := input.Replace('*', ''); input := input.Replace('(', ''); input := input.Replace(')', ''); input := input.Replace('[', ''); input := input.Replace(']', ''); input := input.Replace('{', ''); input := input.Replace('}', ''); input := input.Replace(':', ''); input := input.Replace(';', ''); input := input.Replace('"', ''); input := input.Replace('''', ''); input := input.Replace('<', ''); input := input.Replace('>', ''); input := input.Replace('?', ''); input := input.Replace('/', ''); input := input.Replace('\', ''); input := input.Replace('|', ''); input := input.Replace('~', ''); // Process each character for i := 1 to StrLen(input) do if input[i] = ' ' then capitalizeNext := true else begin if capitalizeNext then result := result + UpperCase(input[i]) else result := result + LowerCase(input[i]); capitalizeNext := false; end; exit(result); end; // Initialize default values when page opens trigger OnOpenPage() begin APIVersion := 'v2.0'; APIPublisher := 'nwt'; APIGroup := 'general'; Prefix := 'nwtAPI'; ODataKeyFields := 'SystemId'; Extensible := 'true'; DelayedInsert := 'true'; InsertAllowed := 'true'; DeleteAllowed := 'true'; ModifyAllowed := 'true'; end; var TableID: Integer; Prefix: Text; GeneratedCode: Text; APIVersion: Text; APIPublisher: Text; APIGroup: Text; EntityCaption: Text; EntitySetCaption: Text; EntityName: Text; EntitySetName: Text; SourceTable: Text; ODataKeyFields: Text; Extensible: Text; DelayedInsert: Text; InsertAllowed: Text; DeleteAllowed: Text; ModifyAllowed: Text; } |
How the Code Works
Let me break down the key components:
1. The Field Record System
al
|
1 2 3 |
fieldRecord.SetRange(TableNo, TableID); if not fieldRecord.FindSet() then Error('Table ID %1 not found', TableID); |
This uses Business Central’s built-in Field record to dynamically read all field metadata from any table. The Field table is a system table that contains information about every field in every table in BC.
2. Automatic Entity Name Generation
al
|
1 2 3 |
EntityCaption := Prefix.ToUpper() + ' ' + tableName; EntityName := Prefix + tableName + 'Model'; EntitySetName := Prefix + tableName + 'Models'; |
When you select a table, the tool automatically generates proper entity names following OData conventions (singular for entity, plural for entity set).
3. Dynamic Code Generation
The GenerateAPI action builds the complete API page code string by string, using the Type Helper codeunit for proper line breaks and formatting. This ensures the generated code is properly formatted and ready to use.
4. The ToCamelCase Function
This is the heart of proper API formatting. It:
- Strips out all special characters that aren’t valid in API field names
- Converts spaces to camelCase (e.g., “Customer Name” becomes “customerName”)
- Ensures compliance with OData naming standards
5. Field Loop Generation
al
|
1 2 3 4 |
repeat if fieldRecord.FieldName <> 'Coupled to CRM' then GeneratedCode := GeneratedCode + 'field(' + ToCamelCase(fieldRecord.FieldName) + '; Rec."' + ... until fieldRecord.Next() = 0; |
This loop processes every field in the table, generating a complete field definition with caption and tooltip for each one. It even filters out problematic fields like CRM coupling fields.
Tips for Using the Generator
- Review Generated Code: While the generator produces valid code, always review it before deploying to ensure it meets your specific requirements.
- Customize as Needed: You can edit the generated code in the text field before copying it to your project.
- Adjust Page ID: Remember to change the page ID (60101 in the generated code) to match your numbering scheme.
- Security: Don’t forget to set up proper permissions for your API pages in production.
- Testing: Always test your generated APIs with tools like Postman before using them in production integrations.
Conclusion
Building custom APIs in Business Central doesn’t have to be a time-consuming manual process. With this API Generator tool, you can:
- Save 99% of your development time on API creation
- Ensure consistency across all your API endpoints
- Reduce errors from manual typing
- Focus on business logic instead of boilerplate code
Whether you’re building a single API or a hundred, this tool transforms a tedious task into a one-click operation. Give it a try in your next Business Central integration project—you’ll wonder how you ever lived without it!
Have you faced similar challenges with bulk API development? Share your experiences in the comments below!
And if you found this tool useful, feel free to customize it for your organization and share it with your team. Happy coding! 💻✨