Developer Guide - VaultPDF Business Central
Extend VaultPDF with custom AL code. Subscribe to integration events, override templates, customize storage paths, and integrate with third-party systems.
Extension Architecture
VaultPDF is a standard Microsoft Dynamics 365 Business Central extension. It occupies object ID range 77100-77199 and uses published integration events to allow partners and customers to extend its functionality.
Extension Dependency
Partner and customer extensions that build on VaultPDF must declare a dependency in their app.json:
{
"dependencies": [
{
"appId": "00000000-0000-0000-0000-000000000000",
"name": "VaultPDF",
"publisher": "Refract Logic",
"version": "2.0.0.0"
}
]
}Versioning Strategy
VaultPDF uses Major.Minor.Patch versioning. Current version: 2.0.0.1.
| Segment | Triggers Upgrade? | Breaking Changes? |
|---|---|---|
| Major | Always | Yes. Schema changes, removed objects, enum type changes. Upgrade codeunit required. |
| Minor | Sometimes | Rarely. New tables, new fields, new required settings keys. Usually no upgrade codeunit. |
| Patch | No | Never. Bug fixes, compiler warnings, UI-only changes. Can be skipped. |
Guidelines for extensions pinning VaultPDF:
- Pin to the current Major version only, e.g.
"version": "2.0". Minor upgrades install automatically without the customer's dependency changing - When VaultPDF releases a Major version, update the customer's dependency and test thoroughly
- If a Major release changes field types (e.g.,
Text[20]toEnum), VaultPDF includes an upgrade codeunit to migrate existing data. Do not rely on Business Central auto-sync - Call Initialize Default Keys as part of every upgrade runbook on customer systems. It is idempotent and safe
Integration Events
VaultPDF publishes integration events via the codeunit "VaultPDF Template Header" (77100) and "VaultPDF Dispatcher" (77119). All events use [IntegrationEvent(false, false)] — no global subscriptions or bind-on-demand.
Subscribe to events using [EventSubscriber(...)]. Set IsHandled := true to override default behavior.
Template Management Events
OnBeforeGetDefaultLinesTableId
Override which Business Central table stores the lines for a custom document header.
[EventSubscriber(
ObjectType::Codeunit,
Codeunit::"VaultPDF Template Header",
'OnBeforeGetDefaultLinesTableId',
'',
false,
false
)]
local procedure OnBeforeGetDefaultLinesTableId(
HeaderTableId: Integer;
var LinesTableId: Integer;
var IsHandled: Boolean
)
begin
if HeaderTableId <> Database::"My Custom Header" then
exit;
LinesTableId := Database::"My Custom Lines";
IsHandled := true;
end;Use this event when the customer's document structure does not follow the standard Business Central naming convention (e.g., header and lines tables that VaultPDF cannot auto-detect).
Dispatcher Events
OnBeforeGetDefaultFolderName
Override the SharePoint folder name where the dispatcher stores generated PDFs and payloads.
[EventSubscriber(
ObjectType::Codeunit,
Codeunit::"VaultPDF Dispatcher",
'OnBeforeGetDefaultFolderName',
'',
false,
false
)]
local procedure OnBeforeGetDefaultFolderName(
TableId: Integer;
var FolderName: Text;
var IsHandled: Boolean
)
begin
if TableId <> Database::"My Custom Header" then
exit;
FolderName := 'CustomDocuments';
IsHandled := true;
end;Folder naming context:
The folder structure in SharePoint is {SourceSystem}/{FolderName}. For example, if VAULTPDF_SOURCE_SYSTEM = "RefractLogic" and you set FolderName := 'Invoices', documents store at RefractLogic/Invoices/.
OnBeforeGetDocumentIdentifier
Override the document identifier, which controls the PDF filename and the documentId in the dispatcher payload. The default uses the source record's primary key.
[EventSubscriber(
ObjectType::Codeunit,
Codeunit::"VaultPDF Dispatcher",
'OnBeforeGetDocumentIdentifier',
'',
false,
false
)]
local procedure OnBeforeGetDocumentIdentifier(
RecRef: RecordRef;
var DocNo: Text;
var IsHandled: Boolean
)
var
MyHeader: Record "My Custom Header";
begin
if RecRef.Number <> Database::"My Custom Header" then
exit;
RecRef.SetTable(MyHeader);
DocNo := MyHeader."No." + '-' + MyHeader."Reference No.";
IsHandled := true;
end;Settings Management Events
The "VaultPDF Settings Mgt" codeunit (77110) publishes events for reading and transforming settings. Use these to inject values from Azure Key Vault, environment variables, or custom configuration systems instead of Business Central Isolated Storage.
OnBeforeGetSetting
Intercept settings reads and provide custom values. This event fires before the normal Business Central storage lookup.
[EventSubscriber(
ObjectType::Codeunit,
Codeunit::"VaultPDF Settings Mgt",
'OnBeforeGetSetting',
'',
false,
false
)]
local procedure OnBeforeGetSetting(
SettingKey: Code[100];
var Value: Text;
var IsHandled: Boolean
)
var
KeyVaultSecret: Text;
begin
if SettingKey <> 'VAULTPDF_CLIENT_SECRET' then
exit;
// Customer's implementation to fetch from Azure Key Vault
KeyVaultSecret := GetSecretFromKeyVault(SettingKey);
Value := KeyVaultSecret;
IsHandled := true;
end;Common use cases:
- Reading secrets from Azure Key Vault instead of Isolated Storage
- Fetching configuration from a custom settings table
- Overriding settings per company or per user
- Rotating credentials without modifying the VaultPDF System Settings page
OnAfterGetSetting
This event fires after the setting value is read. Use it for logging, auditing, or post-processing without blocking the read operation.
[EventSubscriber(
ObjectType::Codeunit,
Codeunit::"VaultPDF Settings Mgt",
'OnAfterGetSetting',
'',
false,
false
)]
local procedure OnAfterGetSetting(
SettingKey: Code[100];
var Value: Text
)
begin
// Audit the setting read
LogSettingAccess(SettingKey);
// Transform the value (e.g., decrypt or normalize)
Value := NormalizeValue(Value);
end;Extensible Enums
The following enums are marked as extensible and can be extended by partner or customer extensions:
| Enum | Object ID | Shipped Values |
|---|---|---|
VaultPDF Template Type | 77102 | Template, Workflow |
VaultPDF Workflow Enforcement | 77104 | None, Validate, Block |
VaultPDF ESign Mode | 77105 | Sequential, Parallel |
VaultPDF Dispatch Mode | 77189 | Standard, Per Group, Per Record, Single Array, Grouped Array |
Extending an Enum
Example: Add a custom template type for internal approval workflows.
enumextension 77200 "My Template Types" extends "VaultPDF Template Type"
{
value(100; "Custom Approval")
{
Caption = 'Custom Approval Process';
}
}After extending the enum, you can:
- Select
Custom Approvalas a template type in VaultPDF Templates - Subscribe to
OnBeforeGetDocumentIdentifieror other events to implement custom behavior for this type - Surface new template settings fields specific to the customer's template type
Adding VaultPDF Actions to Custom Pages
VaultPDF ships page extensions for 13 standard Business Central document pages (Sales Order, Purchase Invoice, etc.). To enable VaultPDF on a custom page, create a page extension that adds VaultPDF actions to the Processing action group.
Create a Page Extension
pageextension 77201 "My Custom Page Ext" extends "My Custom Page"
{
actions
{
addlast(Processing)
{
action(ProcessVaultDocument)
{
Caption = 'Generate VaultPDF Document';
ApplicationArea = All;
Image = SendTo;
ToolTip = 'Generate a PDF from this record using VaultPDF templates.';
trigger OnAction()
var
Dispatcher: Codeunit "VaultPDF Dispatcher";
RecRef: RecordRef;
begin
RecRef.GetTable(Rec);
Dispatcher.GeneratePdf(RecRef, '');
end;
}
}
}
}Call the Dispatcher
The GeneratePdf procedure takes two parameters:
- RecRef - A RecordRef pointing to the document record to process
- TemplateId - (optional) Specific template ID to use. Pass empty string to show the template gallery
Passing an empty template ID displays the Template Gallery modal, allowing users to select a template interactively.
Test the Integration
Open the customer's custom page in Business Central. The new action appears in the Processing group. Clicking it launches the VaultPDF workflow.
Dispatcher Payload Reference
The payload sent to the Dispatcher has the following structure. The templateSettings object is omitted entirely when no settings are configured on the template.
{
"templatePath": "SourceSystem/Module/templateId",
"payloadPath": "SourceSystem/Module/DOC001_MYTEMPLATE.json",
"options": {
"documentId": "DOC001",
"docType": "SalesOrder",
"generatedBy": "PADMA",
"customColumns": {},
"features": {
"generatedDocuments": true
}
},
"templateSettings": {
"watermark": {
"enabled": true,
"value": "CONFIDENTIAL"
},
"distribution": {
"enabled": true,
"approversEmail": "[email protected]"
},
"delivery": {
"recipients": [
{
"email": "[email protected]",
"displayName": "Recipient Name"
}
]
},
"esign": {
"enabled": true,
"signingMode": "sequential",
"expiresInMinutes": 10080,
"signers": [
{
"slotIndex": 1,
"role": "Signer",
"email": "[email protected]",
"namePrefill": "John Doe"
}
]
},
"approvalPanel": {
"user": "approver-id"
},
"workflow": {
"enabled": true,
"recipients": [
{
"email": "[email protected]",
"displayName": "Reviewer"
}
],
"message": "You have been invited to review document DOC001."
}
}
}Payload Fields
| Field | Type | Description |
|---|---|---|
templatePath | string | Full path to the template definition in SharePoint: {SourceSystem}/{Module}/{TemplateId} |
payloadPath | string | Full path to the payload JSON stored in SharePoint: {SourceSystem}/{Module}/{DocumentId}_{TemplateId}.json |
options.documentId | string | Unique identifier for this document. Used in the PDF filename and audit logs |
options.docType | string | Source document type, e.g. SalesOrder, PurchaseInvoice |
options.generatedBy | string | System that generated the request, e.g. PADMA (Business Central) |
templateSettings | object | Routing, workflow, and delivery configuration (omitted if not configured) |
Event Subscription Best Practices
Use Clear, Descriptive Procedure Names
// Good
local procedure OnBeforeGetDocumentIdentifier_InvoiceNumbering(...)
// Avoid
local procedure MyOverride(...)Log Errors Explicitly
begin
if not FetchValueFromKeyVault(SettingKey, KeyVaultValue) then begin
Session.LogMessage('0001',
StrSubstNo('Failed to fetch key vault secret: %1', SettingKey),
Verbosity::Error);
exit;
end;
end;Avoid Blocking Operations in OnAfterGetSetting
The OnAfterGetSetting event should not throw errors or prevent normal operation. Use it only for logging and non-critical transformations.
// Good - logging only
local procedure OnAfterGetSetting_Logging(SettingKey: Code[100]; var Value: Text)
begin
LogSettingRead(SettingKey); // Non-blocking
end;
// Avoid - blocking logic
local procedure OnAfterGetSetting_Bad(SettingKey: Code[100]; var Value: Text)
begin
if ValidateSecretExpiry(Value) = false then
Error('Secret expired'); // Blocks normal flow
end;Common Extension Scenarios
Override the default SharePoint folder naming convention to match the customer's document filing structure.
[EventSubscriber(ObjectType::Codeunit, Codeunit::"VaultPDF Dispatcher", 'OnBeforeGetDefaultFolderName', '', false, false)]
local procedure OnBeforeGetDefaultFolderName(TableId: Integer; var FolderName: Text; var IsHandled: Boolean)
var
CompanyInfo: Record "Company Information";
begin
CompanyInfo.Get();
case TableId of
Database::"Sales Invoice Header":
FolderName := 'AR/' + Format(Today, 0, '<Year4>') + '/' + Format(Today, 0, '<Month,2>');
Database::"Purchase Invoice Header":
FolderName := 'AP/' + Format(Today, 0, '<Year4>') + '/' + Format(Today, 0, '<Month,2>');
else
exit;
end;
IsHandled := true;
end;Fetch dispatcher credentials from Azure Key Vault instead of storing them in Business Central Isolated Storage.
[EventSubscriber(ObjectType::Codeunit, Codeunit::"VaultPDF Settings Mgt", 'OnBeforeGetSetting', '', false, false)]
local procedure OnBeforeGetSetting_KeyVault(SettingKey: Code[100]; var Value: Text; var IsHandled: Boolean)
var
AzureKeyVault: Codeunit "Azure Key Vault";
SecretName: Text;
begin
if SettingKey not in ['VAULTPDF_DISPATCHER_CLIENT_SECRET', 'VAULTPDF_CLIENT_SECRET'] then
exit;
SecretName := 'VaultPDF-' + SettingKey;
if not AzureKeyVault.GetSecret(SecretName, Value) then begin
Session.LogMessage('0002', StrSubstNo('Key Vault secret not found: %1', SecretName), Verbosity::Error);
exit;
end;
IsHandled := true;
end;Automatically register templates into VaultPDF from a JSON configuration file when a custom extension installs.
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Upgrade BC Codeunit", 'OnRun', '', false, false)]
local procedure OnUpgrade_RegisterTemplates()
var
TemplateRegistration: Codeunit "My Template Registration";
begin
TemplateRegistration.RegisterDefaultTemplates();
end;
codeunit 77202 "My Template Registration"
{
procedure RegisterDefaultTemplates()
begin
// Read templates from JSON and populate VaultPDF Templates table
end;
}Maintain separate VaultPDF credentials per company without modifying the global System Settings.
[EventSubscriber(ObjectType::Codeunit, Codeunit::"VaultPDF Settings Mgt", 'OnBeforeGetSetting', '', false, false)]
local procedure OnBeforeGetSetting_MultiCompany(SettingKey: Code[100]; var Value: Text; var IsHandled: Boolean)
var
CompanySettings: Record "My Company VaultPDF Settings";
begin
if not CompanySettings.Get(CompanyName, SettingKey) then
exit;
Value := CompanySettings."Setting Value";
IsHandled := true;
end;Troubleshooting Extension Issues
Event Not Firing
Check:
- Codeunit ID is correct (77100, 77110, 77119)
- Event name spelling matches exactly
- All filter parameters match (usually all empty strings)
- Extension publishes VaultPDF as a dependency in
app.json
IsHandled Not Working
Ensure you set IsHandled := true before setting the return variable:
// Correct
IsHandled := true;
DocNo := MyCustomValue;
// Incorrect - IsHandled set after
DocNo := MyCustomValue;
IsHandled := true;Changes Not Appearing
- Restart the Business Central service (or reload the browser)
- Verify the extension is installed in Extension Management
- Check that the customer's extension is published after VaultPDF
Vault Platform for Dynamics 365 Business Central
Complete integration guide for Business Central consultants and developers. Install, configure, and extend VaultPDF with on-demand PDF generation, SharePoint storage, e-signature workflows, and audit trails.
Troubleshooting - VaultPDF Business Central
Diagnose and resolve common VaultPDF issues. Permission errors, authentication failures, SharePoint connectivity, template problems, and batch operation failures.