Programming Examples
This page provides practical code examples for using the SharePoint Connector API in your own AL extensions. All examples are based on the public UploadToSharePoint sample repository.
App Dependency
Before writing any code, add the SharePoint Connector as a dependency in your app.json:
{
"dependencies": [
{
"id": "23ebd065-b289-4a68-85e3-b8410e360157",
"name": "SharePoint Connector",
"publisher": "hougaard.com",
"version": "6.0.0.0"
}
]
}
Core API Pattern
Every interaction with SharePoint follows the same pattern:
- Authenticate -- call
SP.VerifyAccess()to ensure a valid connection - Get table mapping -- look up the SharePoint site/library configuration for a BC table
- Resolve folder -- map a BC record to a SharePoint folder path
- Operate -- upload, download, list, create folders, generate documents, etc.
var
Mapping: Record "Table Mapping EFQ";
SP: Codeunit "SharePoint EFQ";
Ref: RecordRef;
Folder: Text;
begin
// 1. Authenticate
SP.VerifyAccess();
// 2. Get table mapping
SP.GetTableMapping(Mapping, Ref.Number);
// 3. Resolve folder (true = create if missing)
Folder := SP.GetFolderForRecord(Ref, true);
// 4. Operate (upload, download, etc.)
...
end;
VerifyAccess handles the full authentication flow including token acquisition and refresh. Call it once before your SharePoint operations.
Note: With the Modern App Registration login method the access token from the interactive sign-in only exists as
SecretText, soSP.GetAccessToken()can return an empty text right after signing in. If you call SharePoint yourself, useSP.GetSecretAccessToken()(works for all login methods) orSP.AddAuthorizationHeader(Headers), andSP.HasAccessToken()to check for a token.
Uploading a File
Using the Record-Based Pattern
Upload an InStream to SharePoint for a given record. The connector resolves the destination folder from the table mapping:
procedure UploadAFile(Ref: RecordRef; InS: InStream; FileName: Text)
var
Mapping: Record "Table Mapping EFQ";
SP: Codeunit "SharePoint EFQ";
Folder: Text;
begin
SP.VerifyAccess();
SP.GetTableMapping(Mapping, Ref.Number);
Folder := SP.GetFolderForRecord(Ref, true);
if not SP.UploadFile(Mapping, Folder, InS, FileName, true) then
error(GetLastErrorText());
end;
The last parameter of UploadFile controls whether to overwrite an existing file with the same name.
UploadFile Overloads
UploadFile has three overloads:
// Minimal -- uses default mapping, no overwrite
SP.UploadFile(Folder, InS, FileName);
// With overwrite control, uses default mapping
SP.UploadFile(Folder, InS, FileName, Overwrite);
// Full control -- explicit mapping, folder, and overwrite
SP.UploadFile(Mapping, Folder, InS, FileName, Overwrite);
| Parameter | Type | Description |
|---|---|---|
Mapping |
Record "Table Mapping EFQ" | The site/library configuration (optional -- omit to use default) |
Folder |
Text | Destination folder path in SharePoint |
InS |
InStream | The file content to upload |
FileName |
Text | Name for the file in SharePoint |
Overwrite |
Boolean | true to replace an existing file, false to fail if it exists |
Direct Upload Example
If you already know the folder path and do not need record-based mapping:
procedure UploadToKnownFolder(InS: InStream; FileName: Text)
var
SP: Codeunit "SharePoint EFQ";
begin
SP.VerifyAccess();
if not SP.UploadFile('Shared Documents/Imports', InS, FileName) then
error(GetLastErrorText());
end;
Creating Folders with CreateFolderEx
CreateFolderEx creates a folder at a specific path. When CreateParentIfFailed is true, the connector will attempt to create parent folders if the target path does not exist yet.
SP.CreateFolderEx(Mapping, CompleteFolderName, CreateParentIfFailed): Boolean
| Parameter | Type | Description |
|---|---|---|
Mapping |
Record "Table Mapping EFQ" | The site/library configuration |
CompleteFolderName |
Text | Full folder path to create (e.g., 'Customers/Contoso/2025') |
CreateParentIfFailed |
Boolean | true to auto-create parent folders if they do not exist |
Example: Create a Year Subfolder
procedure CreateYearFolder(Customer: Record Customer; Year: Integer)
var
Mapping: Record "Table Mapping EFQ";
SP: Codeunit "SharePoint EFQ";
Ref: RecordRef;
Folder: Text;
begin
SP.VerifyAccess();
SP.GetTableMapping(Mapping, DATABASE::Customer);
Ref.GetTable(Customer);
Folder := SP.GetFolderForRecord(Ref, true);
// Create e.g. "Customers/Contoso/2025" with parent creation enabled
SP.CreateFolderEx(Mapping, Folder + '/' + Format(Year), true);
end;
Example: Create a Deep Folder Structure
procedure CreateProjectStructure(ProjectFolder: Text)
var
Mapping: Record "Table Mapping EFQ";
SP: Codeunit "SharePoint EFQ";
begin
SP.VerifyAccess();
SP.GetTableMapping(Mapping, DATABASE::Job);
// CreateParentIfFailed = true ensures all intermediate folders are created
SP.CreateFolderEx(Mapping, ProjectFolder + '/Documents/Contracts', true);
SP.CreateFolderEx(Mapping, ProjectFolder + '/Documents/Invoices', true);
SP.CreateFolderEx(Mapping, ProjectFolder + '/Drawings', true);
end;
Listing and Downloading Files
Folder content retrieval is split into two steps. Step one is read-only and can be used inside a PageBackgroundTask. Step two populates a temporary record set.
procedure GetFiles(Ref: RecordRef)
var
FolderContent: Record "SharePoint File EFQ" temporary;
Mapping: Record "Table Mapping EFQ";
SP: Codeunit "SharePoint EFQ";
Folder: Text;
InS: InStream;
begin
SP.VerifyAccess();
SP.GetTableMapping(Mapping, Ref.Number);
Folder := SP.GetFolderForRecord(Ref, true);
// Two-step retrieval: step 1 is read-only (background-task safe)
if SP.GetFolderContentStepTwo(
SP.GetFolderContentStepOne(Mapping, Folder),
FolderContent) then begin
if FolderContent.FindFirst() then begin
SP.DownloadFile(Mapping, FolderContent, InS);
DownloadFromStream(InS, '', '', '', FolderContent.Name);
end;
end;
end;
Checking Whether Files Exist
Use the same two-step retrieval to check for files without downloading them:
procedure HasFiles(Ref: RecordRef): Boolean
var
FolderContent: Record "SharePoint File EFQ" temporary;
Mapping: Record "Table Mapping EFQ";
SP: Codeunit "SharePoint EFQ";
Folder: Text;
begin
SP.VerifyAccess();
SP.GetTableMapping(Mapping, Ref.Number);
Folder := SP.GetFolderForRecord(Ref, true);
if SP.GetFolderContentStepTwo(
SP.GetFolderContentStepOne(Mapping, Folder),
FolderContent) then
if FolderContent.FindFirst() then
exit(true);
end;
Tip: When checking multiple records in a loop, call
VerifyAccessonce before the loop -- it handles token refresh internally.
Creating Folders and Subfolders
Create the SharePoint folder for a record and its configured subfolders:
procedure CreateFolderAndAutoFolders(Ref: RecordRef)
var
Mapping: Record "Table Mapping EFQ";
SP: Codeunit "SharePoint EFQ";
Folder: Text;
begin
SP.VerifyAccess();
SP.GetTableMapping(Mapping, Ref.Number);
Folder := SP.GetFolderForRecord(Ref, true);
SP.CreateSubfolders(Mapping, Ref.Number(), Folder);
end;
Bulk Folder Creation with a Report
A processing-only report that iterates records and creates SharePoint folders for each. Call VerifyAccess once in OnPreDataItem:
report 50100 "Create Item Folders"
{
ApplicationArea = All;
Caption = 'Create Item Folders';
UsageCategory = Tasks;
ProcessingOnly = true;
dataset
{
dataitem(Item; Item)
{
RequestFilterFields = "No.", "Inventory Posting Group";
trigger OnAfterGetRecord()
var
Mapping: Record "Table Mapping EFQ";
Ref: RecordRef;
Folder: Text;
begin
Ref.GetTable(Item);
Folder := SP.GetFolderForRecord(Ref, true);
SP.GetTableMapping(Mapping, DATABASE::Item);
SP.CreateSubfolders(Mapping, DATABASE::Item, Folder);
end;
trigger OnPreDataItem()
begin
SP.VerifyAccess();
end;
}
}
var
SP: Codeunit "SharePoint EFQ";
}
Generating Documents from Templates
Generate a document from a SharePoint template and populate its metadata columns:
procedure GenerateFile(Ref: RecordRef)
var
Mapping: Record "Table Mapping EFQ";
Template: Record "Document Template EFQ";
SP: Codeunit "SharePoint EFQ";
Folder: Text;
NewFileName: Text;
begin
SP.VerifyAccess();
SP.GetTableMapping(Mapping, Ref.Number);
Folder := SP.GetFolderForRecord(Ref, true);
// Load the template (table no, report id, code)
Template.Get(18, 107, '');
NewFileName := SP.GenerateDocument(Template, Ref, Folder, 'new filename.pdf');
if NewFileName <> '' then
SP.FilloutCustomColumns(Mapping, Folder, NewFileName, Ref)
else
error('The report did not produce any output');
end;
Copying Files
The connector exposes two copy operations, one for copying within a single SharePoint document library and one for copying across libraries (or across sites). Both take an existing file represented by a Record "SharePoint File EFQ" and a destination folder path.
Copy Within the Same Document Library
CopyFileSameDocumentLibrary performs a server-side copy by calling the SharePoint REST API's CopyTo endpoint. Because no bytes travel through Business Central, this is the fastest option when source and destination live in the same document library on the same site.
SP.CopyFileSameDocumentLibrary(Mapping, SPFile, DestinationFolder): Boolean
| Parameter | Type | Description |
|---|---|---|
Mapping |
Record "Table Mapping EFQ" | The table mapping that identifies the site/library of both the source and the destination |
SPFile |
Record "SharePoint File EFQ" | The source file record (as returned by GetFolderContentStepTwo) |
DestinationFolder |
Text | Full server-relative folder path to copy the file into (trailing slash is tolerated) |
The method returns true on success and false (or throws a localized error in GUI sessions) when the API rejects the copy. The destination file name is preserved from SPFile.Name, and the call is non-overwriting (bOverwrite=false) -- if a file with the same name already exists in the destination folder, the copy fails.
procedure DuplicateFileToArchive(Mapping: Record "Table Mapping EFQ"; SPFile: Record "SharePoint File EFQ"; CurrentFolder: Text)
var
SP: Codeunit "SharePoint EFQ";
ArchiveFolder: Text;
begin
SP.VerifyAccess();
ArchiveFolder := CurrentFolder + '/Archive';
SP.CreateFolderEx(Mapping, ArchiveFolder, true);
if not SP.CopyFileSameDocumentLibrary(Mapping, SPFile, ArchiveFolder) then
error('Copy to archive failed.');
end;
Because the server handles the copy, large files are unaffected by BC stream limits and the operation is nearly instantaneous regardless of file size.
Copy Between Different Libraries or Sites
CopyFileDifferentLibraries is a download-then-upload helper: it streams the source file through Business Central and uploads it to a different table mapping. Use it when the destination is on a different SharePoint site, a different document library, or simply governed by a different table mapping.
SP.CopyFileDifferentLibraries(SourceMapping, SPFile, DestinationMapping, DestinationFolder): Boolean
| Parameter | Type | Description |
|---|---|---|
SourceMapping |
Record "Table Mapping EFQ" | The mapping that identifies the site/library of the source file |
SPFile |
Record "SharePoint File EFQ" | The source file record |
DestinationMapping |
Record "Table Mapping EFQ" | The mapping that identifies the target site/library |
DestinationFolder |
Text | Folder path inside the destination library to place the new file |
The method internally calls DownloadFile on the source and UploadFile on the destination, so the copy goes through BC. Plan for the associated bandwidth and time when moving large files or running in batch. The destination file name is preserved from SPFile.Name, and the upload is non-overwriting.
procedure CopyCustomerFileToVendorSite(CustomerSPFile: Record "SharePoint File EFQ"; VendorNo: Code[20])
var
SP: Codeunit "SharePoint EFQ";
CustomerMapping: Record "Table Mapping EFQ";
VendorMapping: Record "Table Mapping EFQ";
Vendor: Record Vendor;
Ref: RecordRef;
VendorFolder: Text;
begin
SP.VerifyAccess();
SP.GetTableMapping(CustomerMapping, DATABASE::Customer);
SP.GetTableMapping(VendorMapping, DATABASE::Vendor);
Vendor.Get(VendorNo);
Ref.GetTable(Vendor);
VendorFolder := SP.GetFolderForRecord(Ref, true);
if not SP.CopyFileDifferentLibraries(CustomerMapping, CustomerSPFile, VendorMapping, VendorFolder) then
error('Cross-site copy failed.');
end;
Choosing Between the Two
| Scenario | Use |
|---|---|
| Both source and destination are in the same table mapping / site / library | CopyFileSameDocumentLibrary (faster, server-side) |
Destination is a different site or document library (different Site Name, different library, or multi-site routing) |
CopyFileDifferentLibraries |
| You need to overwrite an existing destination file | Neither method overwrites; delete the destination first or upload under a different name |
Both methods expect VerifyAccess to have been called and a valid mapping to exist for the source. Neither method populates metadata columns on the destination -- call FilloutCustomColumns after a copy if the destination needs the same metadata values as the source.
Renaming Files and Folders
The connector exposes server-side rename for both files and folders. Both methods call the SharePoint REST API's MoveTo endpoint with a parent path computed from the original location, so they rename in place rather than moving across folders.
Renaming a File
SP.RenameFile(Mapping, SPFile, NewName): Boolean
| Parameter | Type | Description |
|---|---|---|
Mapping |
Record "Table Mapping EFQ" | The mapping identifying the site/library of the file |
SPFile |
Record "SharePoint File EFQ" | The source file record (its ServerRelativeUrl is used to compute the parent path) |
NewName |
Text | New file name; sanitized via SanitizeNameNoDot so reserved SharePoint characters are stripped |
procedure RenameTempInvoiceToFinal(Mapping: Record "Table Mapping EFQ"; SPFile: Record "SharePoint File EFQ"; InvoiceNo: Code[20])
var
SP: Codeunit "SharePoint EFQ";
begin
SP.VerifyAccess();
if not SP.RenameFile(Mapping, SPFile, 'Invoice ' + InvoiceNo + '.pdf') then
error('Could not rename the file.');
end;
RenameFile raises an error if NewName is empty (after sanitization) or if SharePoint returns an error message. The call sets the request's IF-MATCH header to *, so it does not require knowledge of the file's ETag.
Renaming a Folder
SP.RenameFolder(Mapping, Folder, NewName): Boolean
| Parameter | Type | Description |
|---|---|---|
Mapping |
Record "Table Mapping EFQ" | The mapping identifying the site/library of the folder |
Folder |
Text | Server-relative path of the folder to rename (trailing slash is tolerated) |
NewName |
Text | New folder name; sanitized via SanitizeName (in Strict mode dots are replaced -- unlike SanitizeNameNoDot, used for file names, which preserves dots) |
procedure RenameProjectFolder(Mapping: Record "Table Mapping EFQ"; FolderPath: Text; NewProjectName: Text)
var
SP: Codeunit "SharePoint EFQ";
begin
SP.VerifyAccess();
if not SP.RenameFolder(Mapping, FolderPath, NewProjectName) then
error('Could not rename the folder.');
end;
Both rename methods are server-side -- no file content travels through BC -- so they are fast regardless of folder/file size. Both fail (and surface the SharePoint error message in GUI sessions) when the target name already exists in the parent path.
Customizing Filename Sanitization
SharePoint has restrictions on which characters are allowed in file and folder names. Subscribe to the connector's events to override the default sanitization:
codeunit 50101 "Override SharePoint Character"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"SharePoint EFQ",
'OnBeforeSanitizeNameNoDot', '', true, true)]
local procedure OnBeforeSanitizeNameNoDot(var Name: Text; var Handled: Boolean)
var
Setup: Record "SharePoint Setup EFQ";
i: Integer;
begin
if Setup.Get() then
case Setup.NameSanitizing of
Setup.NameSanitizing::Strict:
begin
for i := 1 to StrLen(Name) do
if Name[i] in ['''', '!', '@', '$', '%', '^', '&',
'*', '(', ')', '_', '+', '=', '-', '[', ']',
'{', '}', '\', '|', '"', ';', ':', '<', '>',
',', '/', '?', '#'] then
Name[i] := '_';
Handled := true;
end;
Setup.NameSanitizing::Relaxed:
begin
for i := 1 to StrLen(Name) do
if Name[i] in ['"', '*', ':', '<', '>', '?',
'/', '\', '|', '~', '#', '&', '{', '}',
'+', ''''] then
Name[i] := '_';
Handled := true;
end;
end;
end;
}
Set Handled := true to tell the connector to skip its default sanitization.
Building a File Browser Page
The sample repository includes a full SharePoint file browser page (~400 lines) with features including:
- Folder navigation with breadcrumb history
- File upload via dialog and drag-and-drop (base64 encoding)
- File download
- Camera capture and upload
- Word-to-PDF conversion
- Open in SharePoint browser
- Share file links
- Check-out / check-in
- E-signature integration (DocuSign, Adobe Sign)
The full source is available at File Browser.al. It serves as a reference for building custom SharePoint file management UI beyond the standard FactBox.
Key Page Pattern
page 50100 "SharePoint File Browser"
{
PageType = List;
SourceTable = "SharePoint File EFQ";
SourceTableTemporary = true;
// ...
trigger OnOpenPage()
begin
SP.VerifyAccess();
LoadFolderContent();
end;
}
The page uses a temporary "SharePoint File EFQ" record as its source table, populated by calling the two-step GetFolderContent methods.
Printing PDFs from SharePoint
Retrieve files from SharePoint and send them to a printer using ReportManagement.OnAfterDocumentReady:
Report
report 50101 "Print PDF from SharePoint"
{
UsageCategory = ReportsAndAnalysis;
ApplicationArea = All;
DefaultLayout = Word;
WordLayout = 'PrintPDF-Placeholder.docx';
dataset
{
dataitem(SPFile; "SharePoint File EFQ")
{
column(Name; Name) { }
column(ServerRelativeUrl; ServerRelativeUrl) { }
trigger OnAfterGetRecord()
var
InS: InStream;
begin
SP.GetTableMapping(Mapping, GlobalRef);
SP.DownloadFile(Mapping, SPFile, InS);
// Pass stream to print support codeunit
end;
trigger OnPreDataItem()
var
Folder: Text;
begin
SP.VerifyAccess();
SP.GetTableMapping(Mapping, GlobalRef.Number);
Folder := SP.GetFolderForRecord(GlobalRef, true);
if SP.GetFolderContentStepTwo(
SP.GetFolderContentStepOne(Mapping, Folder),
FolderContent) then begin
end;
end;
}
}
var
Mapping: Record "Table Mapping EFQ";
FolderContent: Record "SharePoint File EFQ" temporary;
SP: Codeunit "SharePoint EFQ";
GlobalRef: RecordRef;
}
Print Support Codeunit
codeunit 50102 "Print PDF Support"
{
SingleInstance = true;
var
_InS: InStream;
internal procedure PDFToPrint(var InS: InStream)
begin
_InS := InS;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::ReportManagement,
'OnAfterDocumentReady', '', true, true)]
local procedure OnAfterDocumentReady(ObjectId: Integer;
var TargetStream: OutStream; var Success: Boolean)
begin
if ObjectId = Report::"Print PDF from SharePoint" then begin
CopyStream(TargetStream, _InS);
Success := true;
end;
end;
}
API Reference Summary
| Method | Purpose |
|---|---|
SP.VerifyAccess() |
Authenticate and ensure a valid connection (handles token acquisition and refresh) |
SP.GetTableMapping(Mapping, TableNo) |
Look up SharePoint site/library config for a table |
SP.GetFolderForRecord(Ref, CreateIfMissing) |
Resolve a BC record to a SharePoint folder path |
SP.UploadFile(Folder, InS, FileName) |
Upload a file (no overwrite, default mapping) |
SP.UploadFile(Folder, InS, FileName, Overwrite) |
Upload a file with overwrite control |
SP.UploadFile(Mapping, Folder, InS, FileName, Overwrite) |
Upload a file with explicit mapping and overwrite control |
SP.DownloadFile(Mapping, FileRec, InS) |
Download a file (also available with (FullPath, InS), (SiteName, FullPath, InS), (FileRec, InS), and (SiteName, FileRec, InS) overloads) |
SP.CreateFolderEx(Mapping, CompleteFolderName, CreateParentIfFailed) |
Create a folder, optionally creating parent folders |
SP.GetFolderContentStepOne(Mapping, Folder) |
List folder contents (read-only, background-safe) |
SP.GetFolderContentStepTwo(StepOneResult, FolderContent) |
Populate temp records from step one result |
SP.CreateSubfolders(Mapping, TableNo, Folder) |
Create configured subfolders |
SP.CopyFileSameDocumentLibrary(Mapping, SPFile, DestinationFolder) |
Server-side copy of a file to another folder in the same document library |
SP.CopyFileDifferentLibraries(SourceMapping, SPFile, DestinationMapping, DestinationFolder) |
Copy a file across sites or document libraries (download + upload via BC) |
SP.RenameFile(Mapping, SPFile, NewName) |
Server-side rename of a file in place; new name is sanitized via SanitizeNameNoDot |
SP.RenameFolder(Mapping, Folder, NewName) |
Server-side rename of a folder in place; new name is sanitized via SanitizeName (which, in Strict mode, replaces dots) |
SP.GenerateDocument(Template, Ref, Folder, FileName) |
Generate a document from a Document Template EFQ (Custom Report Layout flavor); a five-argument overload with a trailing Overwrite: Boolean parameter also exists |
SP.GenerateDocumentRL(Template, Ref, Folder, FileName, Overwrite) |
Generate a document from a Document Template RL EFQ (Report Layout flavor) |
SP.FilloutCustomColumns(Mapping, Folder, FileName, Ref) |
Write metadata columns on an uploaded file |