Vault Platform for Dynamics 365 Business Central

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.

SegmentTriggers Upgrade?Breaking Changes?
MajorAlwaysYes. Schema changes, removed objects, enum type changes. Upgrade codeunit required.
MinorSometimesRarely. New tables, new fields, new required settings keys. Usually no upgrade codeunit.
PatchNoNever. 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] to Enum), 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:

EnumObject IDShipped Values
VaultPDF Template Type77102Template, Workflow
VaultPDF Workflow Enforcement77104None, Validate, Block
VaultPDF ESign Mode77105Sequential, Parallel
VaultPDF Dispatch Mode77189Standard, 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:

  1. Select Custom Approval as a template type in VaultPDF Templates
  2. Subscribe to OnBeforeGetDocumentIdentifier or other events to implement custom behavior for this type
  3. 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

FieldTypeDescription
templatePathstringFull path to the template definition in SharePoint: {SourceSystem}/{Module}/{TemplateId}
payloadPathstringFull path to the payload JSON stored in SharePoint: {SourceSystem}/{Module}/{DocumentId}_{TemplateId}.json
options.documentIdstringUnique identifier for this document. Used in the PDF filename and audit logs
options.docTypestringSource document type, e.g. SalesOrder, PurchaseInvoice
options.generatedBystringSystem that generated the request, e.g. PADMA (Business Central)
templateSettingsobjectRouting, 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

On this page