Skip to main content

BC Attachments Generator - Code Templates

This file contains detailed AL code templates for implementing attachments, links, and notes on custom Business Central tables.

Setup Table Fields

Add these fields to your setup table:

field(10; "Enable Attachments"; Boolean)
{
Caption = 'Enable Attachments on [Entity]';
InitValue = true;
}
field(11; "Enable Links"; Boolean)
{
Caption = 'Enable Links on [Entity]';
InitValue = true;
}
field(12; "Enable Notes"; Boolean)
{
Caption = 'Enable Notes on [Entity]';
InitValue = true;
}

Setup Page Fields

Add these fields to your setup page layout:

field("Enable Attachments"; Rec."Enable Attachments")
{
ApplicationArea = All;
ToolTip = 'Specifies if attachments are enabled for [Entity].';
}
field("Enable Links"; Rec."Enable Links")
{
ApplicationArea = All;
ToolTip = 'Specifies if links are enabled for [Entity].';
}
field("Enable Notes"; Rec."Enable Notes")
{
ApplicationArea = All;
ToolTip = 'Specifies if notes are enabled for [Entity].';
}

Enum Extension

enumextension [ID] "[Prefix] Attachment Doc Type" extends "Attachment Document Type"
{
value([ID]; [Prefix][Entity])
{
Caption = '[Entity]';
}
}

Note: Remove spaces from entity name in enum value (e.g., "Statistical Account" → "BCSStatisticalAccount").

Complete Attachment Management Codeunit

codeunit [ID] "[Prefix] Attachment Management"
{
// Event Subscriber: OnBeforeDrillDown (obsolete factbox BC 25.0)
[EventSubscriber(ObjectType::Page, Page::"Document Attachment Factbox", 'OnBeforeDrillDown', '', false, false)]
local procedure OnBeforeDrillDown(DocumentAttachment: Record "Document Attachment"; var RecRef: RecordRef)
begin
case DocumentAttachment."Table ID" of
DATABASE::"[Base Table]":
begin
RecRef.Open(DATABASE::"[Base Table]");
if [Entity].Get(DocumentAttachment."No.") then
RecRef.GetTable([Entity]);
end;
end;
end;

// Event Subscriber: OnAfterOpenForRecRef
[EventSubscriber(ObjectType::Page, Page::"Document Attachment Details", 'OnAfterOpenForRecRef', '', false, false)]
local procedure OnAfterOpenForRecRef(var DocumentAttachment: Record "Document Attachment"; var RecRef: RecordRef)
var
FieldRef: FieldRef;
RecNo: Code[20];
begin
case RecRef.Number of
DATABASE::"[Base Table]":
begin
FieldRef := RecRef.Field([Entity].FieldNo("No."));
RecNo := FieldRef.Value;
DocumentAttachment.SetRange("No.", RecNo);
DocumentAttachment.SetRange("Document Type", DocumentAttachment."Document Type"::[Prefix][Entity]);
end;
end;
end;

// Event Subscriber: OnAfterInitFieldsFromRecRef
[EventSubscriber(ObjectType::Table, Database::"Document Attachment", 'OnAfterInitFieldsFromRecRef', '', false, false)]
local procedure OnAfterInitFieldsFromRecRef(var DocumentAttachment: Record "Document Attachment"; var RecRef: RecordRef)
var
FieldRef: FieldRef;
RecNo: Code[20];
begin
case RecRef.Number of
DATABASE::"[Base Table]":
begin
FieldRef := RecRef.Field([Entity].FieldNo("No."));
RecNo := FieldRef.Value;
DocumentAttachment.Validate("No.", RecNo);
DocumentAttachment.Validate("Document Type", DocumentAttachment."Document Type"::[Prefix][Entity]);
end;
end;
end;

// Visibility Control: Attachments
procedure EntityEnabledAttachments(TableId: Integer): Boolean
begin
case TableId of
DATABASE::"[Base Table]":
begin
if [Prefix][Entity]Setup.Get() then
exit([Prefix][Entity]Setup."Enable Attachments");
exit(false);
end;
else
exit(false);
end;
end;

// Visibility Control: Links
procedure EntityEnabledLinks(TableId: Integer): Boolean
begin
case TableId of
DATABASE::"[Base Table]":
begin
if [Prefix][Entity]Setup.Get() then
exit([Prefix][Entity]Setup."Enable Links");
exit(false);
end;
else
exit(false);
end;
end;

// Visibility Control: Notes
procedure EntityEnabledNotes(TableId: Integer): Boolean
begin
case TableId of
DATABASE::"[Base Table]":
begin
if [Prefix][Entity]Setup.Get() then
exit([Prefix][Entity]Setup."Enable Notes");
exit(false);
end;
else
exit(false);
end;
end;

// Lifecycle: Delete Related Attachments
procedure DeleteRelatedDocumentAttachments([EntityNo]: Code[20])
begin
DocumentAttachment.Reset();
DocumentAttachment.SetRange("Table ID", DATABASE::"[Base Table]");
DocumentAttachment.SetRange("No.", [EntityNo]);
if DocumentAttachment.FindSet() then
DocumentAttachment.DeleteAll(true);
end;

// Lifecycle: Copy Related Attachments (for rename)
procedure CopyRelatedDocumentAttachments(Previous[EntityNo]: Code[20]; New[EntityNo]: Code[20])
var
NewDocumentAttachment: Record "Document Attachment";
begin
DocumentAttachment.Reset();
DocumentAttachment.SetRange("Table ID", DATABASE::"[Base Table]");
DocumentAttachment.SetRange("No.", Previous[EntityNo]);
if DocumentAttachment.FindSet() then
repeat
NewDocumentAttachment := DocumentAttachment;
NewDocumentAttachment."No." := New[EntityNo];
NewDocumentAttachment.Insert(true);
until DocumentAttachment.Next() = 0;
end;

var
[Entity]: Record "[Base Table]";
[Prefix][Entity]Setup: Record "[Prefix] [Entity] Setup";
DocumentAttachment: Record "Document Attachment";
}

Table Extension Triggers

tableextension [ID] "[Prefix] [Entity]" extends "[Base Table]"
{
trigger OnBeforeRename()
begin
RenameAttachments();
end;

trigger OnBeforeDelete()
begin
[Prefix]AttachmentManagement.DeleteRelatedDocumentAttachments(Rec."No.");
end;

local procedure RenameAttachments()
begin
[Prefix]AttachmentManagement.CopyRelatedDocumentAttachments(xRec."No.", "No.");
[Prefix]AttachmentManagement.DeleteRelatedDocumentAttachments(xRec."No.");
end;

var
[Prefix]AttachmentManagement: Codeunit "[Prefix] Attachment Management";
}

Page Extension: Card Page Factboxes

pageextension [ID] "[Prefix] [Entity] Card" extends "[Base Entity Card]"
{
layout
{
addfirst(factboxes)
{
// Legacy factbox (obsolete BC 25.0)
part("Attached Documents"; "Document Attachment Factbox")
{
ApplicationArea = All;
Caption = 'Attachments';
ObsoleteTag = '25.0';
ObsoleteState = Pending;
ObsoleteReason = 'Replaced by Doc. Attachment List Factbox with multiple file upload support.';
SubPageLink = "Table ID" = const(Database::"[Base Table]"),
"No." = field("No."),
"Document Type" = const([Prefix][Entity]);
Visible = false;
Editable = EnabledAttachments;
}

// Modern factbox (BC 25.0+)
part("Attached Documents List"; "Doc. Attachment List Factbox")
{
ApplicationArea = All;
Caption = 'Documents';
UpdatePropagation = Both;
SubPageLink = "Table ID" = const(Database::"[Base Table]"),
"No." = field("No.");
Visible = EnabledAttachments;
Editable = EnabledAttachments;
}
}

// Control Links factbox visibility
modify(Control1900383207)
{
Visible = EnableLinks;
}

// Control Notes factbox visibility
modify(Control1905767507)
{
Visible = EnableNotes;
}
}

trigger OnOpenPage()
begin
EnabledAttachments := [Prefix]AttachmentManagement.EntityEnabledAttachments(DATABASE::"[Base Table]");
EnableLinks := [Prefix]AttachmentManagement.EntityEnabledLinks(DATABASE::"[Base Table]");
EnableNotes := [Prefix]AttachmentManagement.EntityEnabledNotes(DATABASE::"[Base Table]");
end;

var
[Prefix]AttachmentManagement: Codeunit "[Prefix] Attachment Management";
EnabledAttachments: Boolean;
EnableLinks: Boolean;
EnableNotes: Boolean;
}

Note: Control IDs for Links (1900383207) and Notes (1905767507) may vary between BC versions. Inspect the base page if modify fails.

Page Extension: List Page Factboxes

Use the same structure as the Card Page extension above for the List page.

Multi-Entity Support Pattern

When supporting multiple entities, add cases to each event subscriber:

[EventSubscriber(ObjectType::Page, Page::"Document Attachment Details", 'OnAfterOpenForRecRef', '', false, false)]
local procedure OnAfterOpenForRecRef(var DocumentAttachment: Record "Document Attachment"; var RecRef: RecordRef)
var
FieldRef: FieldRef;
RecNo: Code[20];
begin
case RecRef.Number of
DATABASE::"Entity One":
begin
FieldRef := RecRef.Field(EntityOne.FieldNo("No."));
RecNo := FieldRef.Value;
DocumentAttachment.SetRange("No.", RecNo);
DocumentAttachment.SetRange("Document Type", DocumentAttachment."Document Type"::PrefixEntityOne);
end;
DATABASE::"Entity Two":
begin
FieldRef := RecRef.Field(EntityTwo.FieldNo("No."));
RecNo := FieldRef.Value;
DocumentAttachment.SetRange("No.", RecNo);
DocumentAttachment.SetRange("Document Type", DocumentAttachment."Document Type"::PrefixEntityTwo);
end;
end;
end;

Advanced Patterns

Attachment Count FlowField

field([ID]; "Attachment Count"; Integer)
{
Caption = 'Attachments';
FieldClass = FlowField;
CalcFormula = count("Document Attachment" where("Table ID" = const(Database::"[Base Table]"),
"No." = field("No.")));
Editable = false;
}

Custom Attachment Validation

trigger OnBeforeInsert()
begin
if not ValidateAttachmentPermissions() then
Error('User does not have permission to add attachments to this record.');
end;

local procedure ValidateAttachmentPermissions(): Boolean
var
UserSetup: Record "User Setup";
begin
if UserSetup.Get(UserId) then
exit(UserSetup."Allow Attachments");
exit(false);
end;

Power Automate Integration Event

[IntegrationEvent(false, false)]
local procedure OnAfterAttachmentAdded(var DocumentAttachment: Record "Document Attachment")
begin
end;

[EventSubscriber(ObjectType::Table, Database::"Document Attachment", 'OnAfterInsertEvent', '', false, false)]
local procedure OnAfterAttachmentInsert(var Rec: Record "Document Attachment")
begin
if Rec."Table ID" = DATABASE::"[Base Table]" then
OnAfterAttachmentAdded(Rec);
end;