SharePoint Connector Documentation
Index

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:

  1. Authenticate -- call SP.VerifyAccess() to ensure a valid connection
  2. Get table mapping -- look up the SharePoint site/library configuration for a BC table
  3. Resolve folder -- map a BC record to a SharePoint folder path
  4. 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, so SP.GetAccessToken() can return an empty text right after signing in. If you call SharePoint yourself, use SP.GetSecretAccessToken() (works for all login methods) or SP.AddAuthorizationHeader(Headers), and SP.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 VerifyAccess once 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;
}
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