Automated Document Generation - REFRACT PDF Business Central
Trigger REFRACT PDF document generation automatically on posting, from external systems via API, or programmatically from AL code. Covers the Generation Queue, job setup, and monitoring.
Overview
REFRACT PDF supports four ways to trigger document generation. All four paths funnel into the same Generation Queue - a persistent table that the VPF Gen Processor job drains asynchronously.
| Trigger | When to use |
|---|---|
| Interactive gallery | User opens a document and clicks Generate - standard on-demand use |
| Auto-generate on post | Template flag queues generation automatically when a BC document is posted |
| External API | An integrated system (ERP, custom app, integration platform) calls the BC API after creating or posting a document |
| AL code | A partner or customer extension calls the queue table helper directly |
The first three scenarios are covered in detail below. The interactive gallery is described in Processing Documents.
Architecture: The Generation Queue
The Generation Queue (table 73524588 "VPF Gen Request", caption "REFRACT Generation Request") decouples the act of requesting PDF generation from the act of executing it. Any trigger path inserts a row; the Gen Processor (codeunit 73524611 "VPF Gen Processor"), running as a recurring Job Queue entry, picks up Pending rows in batches (up to 50 per run) and drives each one through the full pipeline (JSON build → SharePoint upload → Dispatcher call → Record Link).
Trigger (Post event / API call / AL code)
↓
Gen Request row (Status = Pending)
↓
Job Queue → VPF Gen Processor (codeunit 73524611)
↓
SharePoint Upload + Dispatcher Call
↓
Status = Completed (or Failed / Dead after retries)Key design properties:
- Trigger-agnostic - the processor does not know or care how a row was created
- Fixed retry count - failed rows are retried up to 3 times (hardcoded when the row is queued) before being marked
Dead; there is no backoff between attempts - Concurrent-safe - the processor marks a row
Processingbefore committing, so parallel Job Queue workers cannot double-process the same request - Non-blocking for posting - the posting subscriber inserts Gen Request rows in a new transaction after posting commits, so a subscriber failure never rolls back a completed posting
Scenario 1: Auto-Generate on Post
Enable this flag on a template to have REFRACT PDF automatically queue a generation request whenever a Business Central document from that template's source table is posted.
Supported posting events:
- Sales Invoice posted from a Sales Order or Sales Invoice
- Sales Credit Memo
- Purchase Invoice
- Purchase Credit Memo
Enable Auto Generate on Post on the Template
Open REFRACT Templates and open the template you want to automate.
In the Variants subform, select the variant to automate (usually the default variant) and click Edit Variant Settings. In the Dispatch section, enable the Auto Generate on Post toggle.
Standard Dispatch Mode Only
Auto Generate on Post is only available for variants with Dispatch Mode = Standard. Batch modes (Per Group, Per Record, Single Array, Grouped Array) are not supported for posting automation because each posted document generates exactly one header record - batch modes require a filtered record set selected at dispatch time.
Verify that the template's Source Table ID maps to the correct Business Central document table. The posting subscriber matches on this table ID:
| Document type | Source Table ID |
|---|---|
| Sales Invoice | 112 (Sales Invoice Header) |
| Sales Credit Memo | 114 (Sales Cr.Memo Header) |
| Purchase Invoice | 122 (Purch. Inv. Header) |
| Purchase Credit Memo | 124 (Purch. Cr. Memo Hdr.) |
Create the VPF Gen Processor Job Queue Entry
The posting subscriber inserts queue rows, but nothing processes them unless the Gen Processor job is running.
Search for Job Queue Entries in Business Central and click New.
Configure the entry with these values:
| Field | Value |
|---|---|
| Object Type to Run | Codeunit |
| Object ID to Run | 73524611 |
| Object Caption to Run | VPF Gen Processor |
| Recurring Job | Yes |
| Run on Mondays ... Sundays | Enable all days (or the days matching your posting schedule) |
| Starting Time | 00:00:00 |
| Ending Time | 23:59:59 |
| No. of Minutes between Runs | 2 (poll every 2 minutes) or 5 for lower-frequency environments |
| Status | Ready |
One Job Entry is Sufficient
A single recurring Job Queue entry processes all pending generation requests for all templates. Do not create one entry per template - this wastes Job Queue worker capacity and can cause contention.
Post a Document and Verify
Post any Sales Order or Purchase Invoice that uses a template with Auto Generate on Post enabled.
After posting, open REFRACT Generation Queue from the Tell Me bar (or via the Generation Queue action on the Template Card).
You should see a new row with:
- Source =
Posting Event - Status =
Pending(orProcessingif the job picked it up quickly, orCompletedif already done) - Template Code = the template that matched the posted document
Within the next Job Queue cycle, the row transitions to Completed and a Record Link (PDF link) is attached to the posted document.
Scenario 2: External System Triggers Generation via API
Use this pattern when an external application - a custom ERP, an integration platform (Power Automate, Logic Apps, Boomi, MuleSoft), or a BC API client - creates or posts a BC document and then wants to trigger REFRACT PDF generation without requiring a user to open Business Central.
API Endpoint
POST /api/refractlogic/vaultpdf/v1.0/companies({companyId})/generationRequestsThis is page 73524613 "VPF Gen Request API" (APIPublisher='refractlogic', APIGroup='vaultpdf', APIVersion='v1.0', EntityName='generationRequest'), reading and inserting into table 73524588 "VPF Gen Request".
Authentication: Standard BC OData API authentication (OAuth 2.0 with Dynamics 365 Business Central scope, or Basic auth for on-premises).
Request body:
{
"templateCode": "PINV-STANDARD",
"tableId": 122,
"docNo": "PINV-00001",
"externalRef": "ERP-JOB-REF-789"
}| Field | Type | Required | Description |
|---|---|---|---|
templateCode | string | Yes | REFRACT PDF template code, e.g. PINV-STANDARD. Must exist and be enabled. |
tableId | integer | Yes | Business Central table ID for the posted document. See table IDs below. |
docNo | string | Yes | The document number (No. field) of the posted document. |
externalRef | string | No | Free-text reference from the calling system (e.g. correlation ID, job number). Stored on the queue row for traceability. |
Common table IDs:
| Document | Table ID |
|---|---|
| Sales Invoice Header | 112 |
| Sales Credit Memo Header | 114 |
| Purchase Invoice Header | 122 |
| Purchase Credit Memo | 124 |
| Sales Order Header (pre-post) | 36 |
| Purchase Header (pre-post) | 38 |
Response (201 Created):
{
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"templateCode": "PINV-STANDARD",
"tableId": 122,
"docNo": "PINV-00001",
"status": "Pending",
"externalRef": "ERP-JOB-REF-789",
"createdAt": "2026-06-20T14:32:00Z"
}Polling for Completion
Poll the returned requestId to track processing:
GET /api/refractlogic/vaultpdf/v1.0/companies({companyId})/generationRequests({requestId})Response when complete:
{
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "Completed",
"blobPath": "RefractLogic/PurchaseInvoices/PINV-00001_PINV-STANDARD.pdf",
"correlationId": "vpdf-corr-xyz",
"processedAt": "2026-06-20T14:33:15Z"
}| Status | Meaning |
|---|---|
Pending | Queued; not yet picked up by the processor |
Processing | Processor is actively working on this request |
Completed | PDF generated and stored; blobPath and correlationId are populated |
Failed | Processing failed; errorMessage contains the reason; will be retried |
Dead (Max Retries Reached) | All retry attempts exhausted; manual intervention required |
Polling Interval Recommendation
The Gen Processor runs every 2 - 5 minutes by default. Poll at a 30 - 60 second interval and implement an overall timeout of 15 - 30 minutes for large documents or documents involving workflow routing.
End-to-End Example: External System Posts an AP Invoice
This is the canonical scenario: an external procurement system creates and posts a Purchase Invoice via the BC standard API, then triggers REFRACT PDF generation.
Create and post the Purchase Invoice using the standard BC purchaseInvoices API (or equivalent custom API). Retrieve the posted invoice's No. from the response.
Request PDF generation by calling the REFRACT generation API with the invoice number and the appropriate template code.
POST /api/refractlogic/vaultpdf/v1.0/companies({companyId})/generationRequests
Content-Type: application/json
{
"templateCode": "PINV-STANDARD",
"tableId": 122,
"docNo": "PINV-00001",
"externalRef": "PROC-SYSTEM-JOB-42"
}Store the requestId from the 201 response. Use it to poll for completion.
Poll until status = Completed and use the blobPath or open the REFRACT Lifecycle portal via correlationId to retrieve the generated PDF.
Power Automate / Logic Apps Integration
To integrate with Power Automate or Azure Logic Apps, use the Business Central connector's Call a Bound Action action - or make a direct HTTP call to the OData endpoint using the HTTP with Microsoft Entra ID connector.
Recommended flow pattern:
- Trigger: When a record is created/modified in Business Central (or on a schedule)
- Action: POST to
generationRequestsusing HTTP connector - Action: Do-Until loop polling
GET generationRequests({requestId})untilstatus = Completedor timeout - Action: Send Teams/email notification with
blobPath
Scenario 3: AL Developer Queues Programmatically
A partner or customer extension can queue generation requests directly from AL code without going through a user action or a posting event.
Using the Public API: QueueGenerationForPosting
QueueGenerationForPosting(TableId: Integer; var SourceRecRef: RecordRef) on codeunit 73524612 "VPF Posting Subscriber" is the same path the built-in posting subscriber uses. It does not take a template code - it queues one generation request for every enabled template variant configured with Auto Generate on Post whose source table matches TableId. Use it when you want "everything configured for this table" behavior from your own posting hook:
local procedure TriggerGenerationForMyDoc(PostedHeader: Record "My Posted Header")
var
PostingSubscriber: Codeunit "VPF Posting Subscriber";
RecRef: RecordRef;
begin
RecRef.GetTable(PostedHeader);
PostingSubscriber.QueueGenerationForPosting(Database::"My Posted Header", RecRef);
end;Calling it fires OnBeforeQueueForTable (described below) before any variant is queued.
OnBeforeQueueForTable
Published by codeunit 73524612 "VPF Posting Subscriber". Fires once per table before the posting subscriber queues any generation requests for it. The real signature has no template-code parameter - at this point no specific template or variant has been chosen yet, so a subscriber can only skip the default behavior entirely, not redirect it to a substitute template:
[EventSubscriber(ObjectType::Codeunit, Codeunit::"VPF Posting Subscriber",
'OnBeforeQueueForTable', '', false, false)]
local procedure OnBeforeQueueForTable(
TableId: Integer;
var SourceRecRef: RecordRef;
var IsHandled: Boolean)
begin
// Skip the default queue behavior entirely for a specific custom table;
// handle queuing yourself (e.g. call QueueInternal with your own logic).
if TableId = Database::"My Custom Header" then
IsHandled := true;
end;Setting IsHandled := true prevents the default posting subscriber from queuing anything for that table.
Queue a Specific Template Internally
Use QueueInternal(TemplateCode: Code[20]; SourceRecRef: RecordRef) on table 73524588 "VPF Gen Request" when you want to queue one named template for a record, from a background codeunit, upgrade routine, or any non-posting context:
local procedure ReprocessDocument(DocNo: Code[20])
var
InvHeader: Record "Sales Invoice Header";
GenRequest: Record "VPF Gen Request";
RecRef: RecordRef;
begin
InvHeader.Get(DocNo);
RecRef.GetTable(InvHeader);
GenRequest.QueueInternal('SINV-STANDARD', RecRef);
end;Queue from a Custom Posting Flow (Low-Level)
QueueFromPosting(TemplateVariantId: Code[50]; SourceRecRef: RecordRef) on table 73524588 "VPF Gen Request" is the low-level entry point the posting subscriber itself calls, once per matched template variant. TemplateVariantId must match an existing row in table "VPF Template Setup"; the procedure resolves that variant's parent Template Code automatically:
local procedure AfterMyDocumentPosting(var PostedHeader: Record "My Posted Header")
var
GenRequest: Record "VPF Gen Request";
RecRef: RecordRef;
begin
RecRef.GetTable(PostedHeader);
GenRequest.QueueFromPosting('SINV-STANDARD-DEFAULT', RecRef);
end;It sets Source = Posting Event, captures the Record ID directly from the RecordRef, and resolves Doc No. from the record's No. field. It is safe to call within a posting transaction - the Gen Request insert is committed independently.
Queue from an API (Server-Side Validation)
If you need to add validation logic before accepting an API-originated request, create a page extension on page 73524613 "VPF Gen Request API" and add your logic in OnInsertRecord.
Maximum Retries
The Gen Processor retries a failed request up to 3 times before marking it Dead (Max Retries Reached). This count is hardcoded on the "VPF Gen Request" table's OnInsert trigger (if "Max Retries" = 0 then "Max Retries" := 3) and re-applied defensively by the Gen Processor itself. There is no backoff between attempts and no system setting that changes this number for automatic generation.
REFRACTPDF_GEN_QUEUE_MAX_RETRIES does something different
The REFRACTPDF_GEN_QUEUE_MAX_RETRIES system setting exists, but it only prefills the "Max Attempts" field when you schedule a batch job from the Template Card - that value feeds Business Central's own Job Queue Entry retry mechanism for the scheduled task, not the per-record generation retries described above.
Log Retention and Auto-Purge
The Gen Processor (codeunit 73524611 "VPF Gen Processor") purges old log data on every run, controlled by the REFRACTPDF_LOG_RETENTION_DAYS setting (default 90; set to 0 to disable).
What gets purged, older than the retention cutoff:
- "VPF Batch Job Entry" rows with status
Completed,Failed, orPartial, and their linked "VPF Batch Log Detail" rows - "VPF Batch Log Entry" rows, filtered by their own
Log DateTime(independent of any batch job's status) - "VPF Gen Request" rows with status
Completed, filtered byCreated At
What is not purged:
- Batch jobs still
QueuedorRunning, regardless of age - "VPF Gen Request" rows with status
Failed,Dead,Pending, orProcessing, regardless of age
There is no separate "Upload Log" page or table - generation and batch history are recorded on "VPF Batch Log Entries" (page 73524600) and its detail sub-page "VPF Batch Log Detail" (page 73524610).
Monitoring the Generation Queue
Generation Queue Page
Open REFRACT Generation Queue from the Tell Me bar to view all requests across all templates.
From the Template Card, the Generation Queue action in the action bar opens the same list pre-filtered to the current template.
Page columns:
| Column | Description |
|---|---|
| Request ID | Unique identifier (GUID) for this generation request |
| Template Code | The REFRACT PDF template used |
| Doc No. | Document number from the source BC record |
| Status | Current state: Pending, Processing, Completed, Failed, Dead |
| Source | How the request was created: Internal, API, Posting Event, Batch |
| Retry Count | Number of processing attempts so far |
| Error Message | Error text from the last failed attempt |
| Blob Path | SharePoint path of the generated PDF (populated on Completed) |
| Created At | When the request was queued |
| Processed At | When processing completed or last failed |
Status color coding:
| Status | Style |
|---|---|
Completed | Green (Favorable) |
Failed | Red (Unfavorable) |
Dead (Max Retries Reached) | Red (Unfavorable) |
Pending | Standard (Ambiguous) |
Processing | Yellow (Attention) |
Actions
| Action | Purpose |
|---|---|
| Retry Selected | Resets selected Failed or Dead rows back to Pending for reprocessing. Only enabled when selected rows are in a retryable state. |
| Delete Selected | Removes selected rows. Use for cleaning up rows that are no longer needed. |
| Purge Completed | Removes all Completed rows for the current template filter. Use for routine maintenance of the queue table. |
| Filter: Pending | Applies a quick filter showing only rows awaiting processing |
| Filter: Failed / Dead | Shows rows that require attention |
| Clear Filter | Removes the quick filter |
Dead Rows Require Manual Intervention
A row enters Dead (Max Retries Reached) status when all retry attempts have failed. Investigate the Error Message column to identify the root cause (connectivity, template misconfiguration, missing data) before using Retry Selected to requeue the row. Retrying without fixing the underlying issue will exhaust retries again.
Troubleshooting
| Symptom | Likely cause | Resolution |
|---|---|---|
Queue rows stay Pending indefinitely | Gen Processor Job Queue entry is not running or is on hold | Open Job Queue Entries, find codeunit 73524611, confirm status is Ready and not on hold |
All rows show Failed immediately | Dispatcher or SharePoint connection error | Run System Status Check in System Settings; check Error Message on a failed row |
Auto Generate on Post not queuing any rows | Template's Source Table ID does not match the posted document table, or no variant has Auto Generate on Post enabled | Verify the template's Source Table ID; check that a variant is Enabled with Auto Generate on Post on |
| API returns 401 or 403 | OData authentication missing or caller lacks permissions | Ensure the API caller has the VPF-USER permission set; verify OAuth scope includes Dynamics 365 Business Central |
| API returns 400 with "Template Code not found" | templateCode value in the request body does not match any enabled template | Check REFRACT Templates - template must exist and have Enabled = true |
| Row completes but no PDF link on the document | Record Link creation failed | Check the Blob Path field on the queue row - if populated, the PDF exists in SharePoint; the Record Link attachment may have been skipped due to a missing Record ID on the request |
Dead rows accumulating | A systemic error is failing all requests within their fixed 3 attempts | Investigate the root cause from Error Message, fix it, then use Retry Selected to requeue |
Template Configuration - REFRACT PDF Business Central
Configure REFRACT PDF templates for PDF generation, batch dispatch, e-signature, approval workflows, distribution, and multi-hop field traversal in Business Central.
Developer Guide - REFRACT PDF Business Central
Extend REFRACT PDF with custom AL code. Subscribe to integration events, override templates, customize storage paths, and integrate with third-party systems.