SharePoint is Microsoft's enterprise collaboration platform, widely used for document management, intranets, and team sites. Integrating with SharePoint—whether adding custom functionality, automating workflows, or building extensions—requires understanding its architecture and APIs. This guide covers essential SharePoint integration patterns from a senior developer's perspective.
Why SharePoint Integration
SharePoint integration enables:
- Custom Features: Extend beyond built-in capabilities.
- Workflow Automation: Streamline business processes.
- Data Migration: Move content to and from SharePoint.
- External Connections: Integrate line-of-business applications.
- Enhanced UX: Improve user experience with custom UI.
SharePoint Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ SharePoint Site │
├─────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Document │ │ List │ │ Wiki │ │
│ │ Library │ │ │ │ Pages │ │
│ └──────────┘ └──────────┘ └──────────┘ │
├─────────────────────────────────────────────────────────┤
│ Web Parts / Apps │
├─────────────────────────────────────────────────────────┤
│ REST API / CSOM / JSOM │
└─────────────────────────────────────────────────────────┘
REST API Integration
Authentication
// SharePoint Online with Azure AD
using Microsoft.Identity.Client;
public class SharePointClient
{
private readonly string _siteUrl;
private readonly IConfidentialClientApplication _app;
public SharePointClient(string siteUrl, string clientId, string clientSecret, string tenantId)
{
_siteUrl = siteUrl;
_app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.Build();
}
public async Task<string> GetAccessTokenAsync()
{
var scopes = new[] { $"{_siteUrl}/.default" };
var result = await _app.AcquireTokenForClient(scopes).ExecuteAsync();
return result.AccessToken;
}
}
Basic Operations
public class SharePointService
{
private readonly HttpClient _client;
private readonly string _siteUrl;
public async Task<List<DocumentInfo>> GetDocumentsAsync(string libraryName)
{
var endpoint = $"{_siteUrl}/_api/web/lists/getbytitle('{libraryName}')/items";
var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await _client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<SharePointResponse>(content);
return data.Value.Select(item => new DocumentInfo
{
Id = item.Id,
Title = item.Title,
FileRef = item.FileRef
}).ToList();
}
public async Task UploadFileAsync(string libraryName, string fileName, byte[] content)
{
var endpoint = $"{_siteUrl}/_api/web/lists/getbytitle('{libraryName}')" +
$"/RootFolder/Files/add(url='{fileName}',overwrite=true)";
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
request.Content = new ByteArrayContent(content);
await _client.SendAsync(request);
}
public async Task UpdateListItemAsync(string listName, int itemId, Dictionary<string, object> fields)
{
var endpoint = $"{_siteUrl}/_api/web/lists/getbytitle('{listName}')/items({itemId})";
var metadata = new { __metadata = new { type = $"SP.Data.{listName}ListItem" } };
var payload = fields.Concat(new[] { new KeyValuePair<string, object>("__metadata", metadata.__metadata) })
.ToDictionary(x => x.Key, x => x.Value);
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
request.Headers.Add("X-HTTP-Method", "MERGE");
request.Headers.Add("IF-MATCH", "*");
request.Content = new StringContent(
JsonSerializer.Serialize(payload),
Encoding.UTF8,
"application/json"
);
await _client.SendAsync(request);
}
}
Adding Custom Image Upload to Wiki
SharePoint Wiki's default image dialog only supports URL insertion. Here's how to add file upload capability:
The Challenge
SharePoint Services 3.0's built-in Wiki editor lacks image upload functionality—you can only insert images via URL. This requires users to: 1. Upload images separately to a document library 2. Copy the image URL 3. Paste the URL in the Wiki editor
Solution: Custom Upload Dialog
Modify the Rich Text Editor dialog to include file upload:
<%-- UploadImage.aspx - Custom upload handler --%>
<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Page Language="C#" MasterPageFile="~/_layouts/simple.master" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.SharePoint.WebControls" %>
<script runat="server">
protected void UploadButton_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
{
try
{
// Get target library from web.config
string libraryUrl = ConfigurationManager.AppSettings["WikiImageLibrary"];
SPWeb web = SPContext.Current.Web;
SPFolder folder = web.GetFolder(libraryUrl);
string fileName = FileUpload1.FileName;
byte[] fileContent = FileUpload1.FileBytes;
// Upload file
SPFile file = folder.Files.Add(fileName, fileContent, true);
// Return URL to parent dialog
string imageUrl = web.Url + "/" + file.Url;
ClientScript.RegisterStartupScript(
this.GetType(),
"CloseDialog",
$"window.returnValue = '{imageUrl}'; window.close();",
true
);
}
catch (Exception ex)
{
ErrorLabel.Text = "Upload failed: " + ex.Message;
}
}
}
</script>
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
<h2>Upload Image to Wiki</h2>
<asp:FileUpload ID="FileUpload1" runat="server" />
<asp:Button ID="UploadButton" runat="server"
Text="Upload"
OnClick="UploadButton_Click" />
<asp:Label ID="ErrorLabel" runat="server" ForeColor="Red" />
<div style="margin-top: 20px;">
<asp:Image ID="PreviewImage" runat="server" Visible="false" />
</div>
</asp:Content>
Integrating with Rich Text Editor
Modify RteDialog.aspx in the layouts folder to add an upload tab:
// Add to the RteDialog.aspx script section.
function openImageUpload() {
var uploadUrl = '/_layouts/UploadImage.aspx';
var options = {
url: uploadUrl,
width: 500,
height: 300,
dialogReturnValueCallback: function(dialogResult, returnValue) {
if (dialogResult == SP.UI.DialogResult.OK && returnValue) {
// Insert the image with the returned URL.
insertImage(returnValue);
}
}
};
SP.UI.ModalDialog.showModalDialog(options);
}
function insertImage(imageUrl) {
var imgHtml = '<img src="' + imageUrl + '" alt="Uploaded image" />';
// Get the RTE instance and insert.
var editor = RTE.RichTextEditor.getEditor(document.getElementById('txtContent'));
editor.pasteHtml(imgHtml);
}
Configuration
Add to web.config:
<configuration>
<appSettings>
<add key="WikiImageLibrary" value="WikiImages" />
</appSettings>
</configuration>
SharePoint Framework (SPFx) Web Parts
Modern SharePoint uses SPFx for custom development:
// src/webparts/customList/CustomListWebPart.ts
import { Version } from '@microsoft/sp-core-library';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
export interface ICustomListWebPartProps {
listName: string;
}
export default class CustomListWebPart extends BaseClientSideWebPart<ICustomListWebPartProps> {
public render(): void {
this.domElement.innerHTML = `<div id="listItems">Loading...</div>`;
this.loadListItems();
}
private async loadListItems(): Promise<void> {
const endpoint = `${this.context.pageContext.web.absoluteUrl}` +
`/_api/web/lists/getbytitle('${this.properties.listName}')/items`;
try {
const response: SPHttpClientResponse = await this.context.spHttpClient.get(
endpoint,
SPHttpClient.configurations.v1
);
const data = await response.json();
this.renderItems(data.value);
} catch (error) {
this.domElement.innerHTML = `<div>Error loading items: ${error.message}</div>`;
}
}
private renderItems(items: any[]): void {
let html = '<ul>';
items.forEach(item => {
html += `<li>${item.Title}</li>`;
});
html += '</ul>';
this.domElement.querySelector('#listItems')!.innerHTML = html;
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
}
Power Automate (Flow) Integration
Create automated workflows:
{
"definition": {
"triggers": {
"When_a_file_is_created": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['sharepointonline']['connectionId']"
}
},
"method": "get",
"path": "/datasets/@{encodeURIComponent(encodeURIComponent('https://contoso.sharepoint.com/sites/mysite'))}/triggers/onnewfile",
"queries": {
"folderId": "Shared Documents"
}
}
}
},
"actions": {
"Send_email": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['office365']['connectionId']"
}
},
"method": "post",
"path": "/v2/Mail",
"body": {
"To": "[email protected]",
"Subject": "New file uploaded",
"Body": "A new file was uploaded: @{triggerOutputs()['headers']['x-ms-file-name']}"
}
}
}
}
}
}
PnP PowerShell Automation
# Connect to SharePoint Online.
Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/mysite" -Interactive
# Get all documents.
$items = Get-PnPListItem -List "Documents"
foreach ($item in $items) {
Write-Host $item["FileLeafRef"]
}
# Upload the file.
Add-PnPFile -Path "C:\local\document.pdf" -Folder "Shared Documents"
# Create a list item.
Add-PnPListItem -List "Tasks" -Values @{
"Title" = "New Task"
"DueDate" = (Get-Date).AddDays(7)
"AssignedTo" = "[email protected]"
}
# Update permissions.
Set-PnPListItemPermission -List "Documents" -Identity 1 -User "[email protected]" -AddRole "Contribute"
Key Takeaways
- Choose the right API: REST for simplicity, CSOM for complex operations.
- Handle authentication properly: Azure AD for SharePoint Online.
- Use SPFx for modern UI: Web parts and extensions.
- Leverage Power Automate: No-code workflow automation.
- Test in a development tenant: Never test in production.
- Follow governance: Respect site permissions and policies.
SharePoint integration ranges from simple REST calls to complex custom solutions—start simple and add complexity only when needed.