Skip to main content

SharePoint Connector

The SharePoint connector (ctx.connectors.sharepoint) works with SharePoint lists, libraries, files and folders. Locally it calls the SharePoint REST API (_api/web/...) and reshapes the answers into the form the Power Automate SharePoint connector returns, so expressions such as item()?['Status']?['Value'] behave the same locally and once deployed.

Parameters use the Power Automate names: dataset is the site URL, table the list or library GUID.

Authentication​

The connector needs a SharePoint token, not a Microsoft Graph token. Its audience is your SharePoint host (https://contoso.sharepoint.com), and the permission is the delegated AllSites.Write on the SharePoint API (App ID 00000003-0000-0ff1-ce00-000000000000), not Graph's Sites.* permissions. A Graph token is rejected with 401.

Add the permission to your app registration (see App registration) and name the SharePoint host in flowforger.config.json:

{
"auth": {
"clientId": "<client-id>",
"tenantId": "<tenant-id>",
"resources": {
"sharepoint": "https://contoso.sharepoint.com"
}
}
}
flowforger run flow.ff.ts --auth

--auth requests https://contoso.sharepoint.com/AllSites.Write whenever the flow uses a SharePoint action, and fails with "auth.resources.sharepoint is not configured" if the host is missing. One token covers every site on that host. flowforger scopes flow.ff.ts shows what a given flow will request.

With an explicit token​

az account get-access-token --resource "https://contoso.sharepoint.com" --query accessToken -o tsv
flowforger run flow.ff.ts --sp-token <token>

An explicit --sp-token wins over --auth for SharePoint, and SharePoint is then left out of the --auth sign-in, so auth.resources.sharepoint is not needed.

Operations​

Call ctx.connectors.sharepoint.<Operation>(name, params, connectionReferenceName?). The first name in each row is the cloud operation id, which is what the compiled flow contains. The names in brackets are local aliases: they run locally and compile to the cloud id.

List items

OperationNotes
GetItems$filter, $orderby, $top, $skip, $select, $expand. Pagination: see below.
GetItem (GetItemById)One item by id.
PostItem (CreateItem)Create an item. Answers 201 with the item.
PatchItem (UpdateItem)Update an item. Answers with the item.
DeleteItemNo body.
GetItemChangesItem version history. Its output has not been compared with the cloud connector.
GetAllTables (GetLists, GetAllListsAndLibraries), GetListViewsList and view metadata.

Library items (files with their columns)

OperationNotes
GetFileItems (GetFilesPropertiesOnly)Files and their properties. folderPath and includeNestedItems scope it to a folder.
GetFileItem (GetFileProperties)One file's properties.
PatchFileItem (UpdateFileProperties)Update a file's columns; same field format as PatchItem.

Files and folders

OperationNotes
CreateFile, UpdateFile, DeleteFileCreate and update answer with the file's metadata.
GetFileContent, GetFileContentByPathBinary content.
GetFileMetadata, GetFileMetadataByPathFile metadata.
CreateNewFolderCreates every missing level; answers with the folder item.
ListFolder, ListRootFolderFolder entries.
GetFolderMetadata, GetFolderMetadataByPathFolder metadata.
CopyFileAsync (CopyFile), MoveFileAsync (MoveFile), CopyFolderAsync (CopyFolder), MoveFolderAsync (MoveFolder)Answer with the destination's metadata. nameConflictBehavior: 0 fails ("A file or folder with the name '...' already exists at the destination."), 1 replaces (the existing one goes to the recycle bin), 2 keeps both, giving the copy the first free name (report1.pdf, report2.pdf, ...).
ExtractFolderV2Unzips a .zip into a folder. SharePoint REST has no extract call, so the archive is unpacked locally and its entries uploaded. Answers with the archive's top-level entries.

Check in and check out

OperationNotes
CheckOutFile, CheckInFile, DiscardFileCheckOutAddress the library item (table + id). Check-in takes parameter/comment and parameter/checkinType. No body. Checking in a file that is not checked out fails with 400. Use DiscardFileCheckOut, not the local alias DiscardCheckOut, in flows you deploy.

Attachments

OperationNotes
CreateAttachment (AddAttachment)Answers with the attachment { Id, AbsoluteUri, DisplayName }.
GetItemAttachments (GetAttachments)The item's attachments.
GetAttachmentContentBinary content; a missing file is 404 "File not found".
DeleteAttachmentNo body.

Sharing, approval and people

CreateSharingLink, GrantAccess, UnshareItem (StopSharing), SetContentApprovalStatus, GetContentApprovalStatus, ResolvePerson.

Send an HTTP request to SharePoint

HttpRequest (SendHttpRequest) calls any _api/... endpoint with the SharePoint token. See below.

Triggers

SharePoint triggers (GetOnNewItems, GetOnUpdatedItems, ...) are written into the compiled flow but never polled locally. Pass the item you want to test with as the trigger body: flowforger run flow.ff.ts --in item.json. See Connector triggers.

Example​

@Action()
async run(ctx: FlowContext) {
/** @runtimeConfig {"paginationPolicy":{"minimumItemCount":5000}} */
await ctx.connectors.sharepoint.GetItems('GetTasks', {
dataset: 'https://contoso.sharepoint.com/sites/projects',
table: '6f1c2b6a-3f0e-4d7a-9b1e-2a4c8d0e5f71',
'$filter': "Status eq 'Active'",
}, 'shared_sharepointonline');

for (const task of ctx.body('GetTasks')?.['value']) {
await ctx.connectors.sharepoint.PatchItem('MarkReviewed', {
dataset: 'https://contoso.sharepoint.com/sites/projects',
table: '6f1c2b6a-3f0e-4d7a-9b1e-2a4c8d0e5f71',
id: task?.['ID'],
item: { 'Reviewed': true, 'Status/Value': 'In review' },
}, 'shared_sharepointonline');
}
}

More examples: examples/sharepoint/ (one .ff.ts file per operation, e.g. get-items.ff.ts, create-file.ff.ts, folder-management.ff.ts, sharing-workflow.ff.ts).

Output shapes​

The local connector returns items laid out the way the cloud connector returns them:

  • @odata.etag, ItemInternalId and ID come first, then the columns.
  • Choice columns are { "@odata.type": "...SPListExpandedReference", "Id": <choice index>, "Value": "<text>" } (Id is -1 for a fill-in value), followed by a <Column>#Id sibling.
  • Lookup columns are expanded automatically and wrapped the same way: Id is the target item ID, Value its display column.
  • Person or group columns (including Author and Editor) are { "@odata.type": "...SPListExpandedUser", "Claims", "DisplayName", "Email", "Picture", "Department", "JobTitle" }, followed by a <Column>#Claims sibling. Department and JobTitle are always null locally.
  • Multi-value columns are arrays of those objects; an empty one is []. Null columns are left out.
  • Date-only columns are the site-local yyyy-MM-dd; hyperlink columns are the URL.
  • The synthetic fields {Identifier}, {IsFolder}, {Link}, {Name}, {FilenameWithExtension}, {Path}, {FullPath}, {ContentType}, {HasAttachments} and {VersionNumber} are present when you pass no $select.
  • Document library items also get OData__DisplayName, {Thumbnail} (with null image URLs; previews are not rendered locally), {IsCheckedOut}, {DriveId} and {DriveItemId}. A checked-out file carries CheckoutUser.
  • Raw REST extras (Id, GUID, <Field>Id, <Field>StringId, ...) are dropped, and the paging link is @odata.nextLink.

A $select you pass limits which lookup and person columns are expanded; a $expand you pass yourself is left untouched. If the expanded query fails (for example on the lookup column threshold), the connector retries without expansion and still wraps choice columns.

File and folder metadata operations (GetFileMetadata, ListFolder, CreateFile, copy and move, ...) answer with the cloud's metadata shape: { ItemId, Id, Name, DisplayName, Path, LastModified, Size, MediaType, IsFolder, ETag, FileLocator }. Folders have no MediaType, ETag or FileLocator.

Writing items​

PostItem, PatchItem and PatchFileItem accept the designer's field format, translated to REST from the list's field metadata:

You writeSent as
item/Title: 'x'Title
item/Status/Value: 'Done'the choice value
item/Tags: [{ Value: 'a' }, { Value: 'b' }]a multi-choice value
item/Category/Id: 7the lookup CategoryId
item/Owner/Claims: 'anna@contoso.com' (an email, login or claims string)OwnerId, resolved with ensureuser
item/Reviewers: [{ Claims: '...' }]a multi-person value

An object literal under item is flattened to these keys. A column set to an expression that finds nothing is sent as null and clears the column; a null sub-value such as item/Status/Value leaves the column unchanged.

Failures​

A failed call's outputs are { statusCode, body: { status, message } }, as in the cloud, with the cloud's rewrites:

  • a missing item: 404 "Item Not Found"
  • a missing list: 404 "List not found"
  • an unknown column in a query: 400 (SharePoint REST answers 500)

The cloud appends clientRequestId/serviceRequestId lines to the message; the local message has no such lines. See Outputs and failures for reading these in a catch scope.

Send an HTTP request to SharePoint​

await ctx.connectors.sharepoint.HttpRequest('GetWebTitle', {
dataset: 'https://contoso.sharepoint.com/sites/projects',
'parameters/method': 'GET',
'parameters/uri': '_api/web?$select=Title',
}, 'shared_sharepointonline');

await ctx.compose('Title', ctx.body('GetWebTitle')?.['d']?.['Title']);

The answer follows the cloud connector:

  • The outputs are { statusCode, headers, body }.
  • The default Accept is application/json;odata=verbose, so JSON answers are wrapped in d (body('X')?['d']).
  • Top-level null properties are dropped from a JSON body.
  • Any non-JSON answer is binary content { "$content-type", "$content" }.
  • An empty answer (204 from a MERGE or DELETE) has no body, so ctx.body('X') is null.

A failure's body is { status, message, source, errors }.

Pagination and large lists​

Without a pagination policy, GetItems and GetFileItems return one page. With @runtimeConfig {"paginationPolicy":{"minimumItemCount":N}} on the call, the engine follows @odata.nextLink until at least N items are collected (whole pages, so the count can exceed N), and the result is { value } with no next link, as in the cloud. Values above 5,000 need a premium plan in the cloud, and flowforger validate warns about them (PAGINATION_THRESHOLD).

Folder-scoped GetFileItems (a folderPath with no $filter, $orderby, $top or $skip) is sent as a CAML query scoped to the folder, not as a FileDirRef filter, so it works on libraries above the 5,000-item list view threshold. includeNestedItems: false lists only the folder's direct children.

Running locally​

flowforger run flow.ff.ts --auth
flowforger run flow.ff.ts --sp-token <token>

To run without touching SharePoint, mock the actions (--mock GetTasks=tasks.json), or use --live to allow only the reads you name; see Local testing.

Package​

@flowforger/connectors-sharepoint