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.
With --auth (recommended)
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
| Operation | Notes |
|---|---|
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. |
DeleteItem | No body. |
GetItemChanges | Item version history. Its output has not been compared with the cloud connector. |
GetAllTables (GetLists, GetAllListsAndLibraries), GetListViews | List and view metadata. |
Library items (files with their columns)
| Operation | Notes |
|---|---|
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
| Operation | Notes |
|---|---|
CreateFile, UpdateFile, DeleteFile | Create and update answer with the file's metadata. |
GetFileContent, GetFileContentByPath | Binary content. |
GetFileMetadata, GetFileMetadataByPath | File metadata. |
CreateNewFolder | Creates every missing level; answers with the folder item. |
ListFolder, ListRootFolder | Folder entries. |
GetFolderMetadata, GetFolderMetadataByPath | Folder 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, ...). |
ExtractFolderV2 | Unzips 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
| Operation | Notes |
|---|---|
CheckOutFile, CheckInFile, DiscardFileCheckOut | Address 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
| Operation | Notes |
|---|---|
CreateAttachment (AddAttachment) | Answers with the attachment { Id, AbsoluteUri, DisplayName }. |
GetItemAttachments (GetAttachments) | The item's attachments. |
GetAttachmentContent | Binary content; a missing file is 404 "File not found". |
DeleteAttachment | No 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,ItemInternalIdandIDcome first, then the columns.- Choice columns are
{ "@odata.type": "...SPListExpandedReference", "Id": <choice index>, "Value": "<text>" }(Idis-1for a fill-in value), followed by a<Column>#Idsibling. - Lookup columns are expanded automatically and wrapped the same way:
Idis the target item ID,Valueits display column. - Person or group columns (including
AuthorandEditor) are{ "@odata.type": "...SPListExpandedUser", "Claims", "DisplayName", "Email", "Picture", "Department", "JobTitle" }, followed by a<Column>#Claimssibling.DepartmentandJobTitleare 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 carriesCheckoutUser. - 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 write | Sent 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: 7 | the 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 answers500)
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
Acceptisapplication/json;odata=verbose, so JSON answers are wrapped ind(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 (
204from a MERGE or DELETE) has nobody, soctx.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