SharePoint Embedded: bringing SharePoint document management into your custom app

My first blog post on this site was about MOSS 2007. That was 2012, and even then I was dealing with a platform that was simultaneously incredibly powerful and incredibly frustrating to integrate with. Over the years I’ve gone through every wave of “now you can get SharePoint content into your app.” CSOM. Provider-Hosted Apps (and if you’ve read my post about the TLS 1.2 and SameSite cookie nightmare, you know how much fun that was). REST APIs. Microsoft Graph. Every time, it felt like you were fighting the platform. You wanted documents with versioning and permissions, and SharePoint wanted you to use its UI. SharePoint Embedded changes that, and honestly, it’s about time.
What is SharePoint Embedded
SharePoint Embedded is a headless SharePoint content repository. Let that sink in for a second. Headless SharePoint. Your app owns the user experience (every button, every file picker, every drag-and-drop interaction) while SharePoint handles the heavy lifting behind the scenes: storage, versioning, permissions, compliance, eDiscovery, and now even AI through SharePoint Embedded Agents. The user never sees the SharePoint interface. They don’t even know SharePoint is involved. They just see your app, with enterprise-grade document management baked in.
This way, you get everything SharePoint is genuinely great at (and let’s be honest, the document management engine in SharePoint is rock solid) without having to expose the SharePoint chrome to your users. It’s the separation of concerns I’ve been wanting since I first started wrapping SharePoint in custom UIs back in 2014.
Setting it up
Let’s get started with the basics. You work with SharePoint Embedded through the Microsoft Graph API, which means if you’ve been following along with any of my recent posts about stopping the use of client secrets, you already know the authentication story. You create a container (think of it as an isolated document library that only your app can see), and then you upload files into it. Here’s what that looks like in C#:
using Microsoft.Graph;
using Azure.Identity;
var credential = new DefaultAzureCredential();
var graphClient = new GraphServiceClient(credential);
var container = await graphClient
.Storage
.FileStorage
.Containers
.PostAsync(new FileStorageContainer
{
DisplayName = "Customer Contracts - Contoso",
Description = "All contract documents for Contoso project",
ContainerTypeId = Guid.Parse("your-container-type-id")
});
Console.WriteLine($"Container created: {container!.Id}");
using var fileStream = File.OpenRead("./contracts/contoso-msa-2026.pdf");
var uploadedFile = await graphClient
.Storage
.FileStorage
.Containers[container.Id]
.Drive
.Root
.ItemWithPath("contracts/contoso-msa-2026.pdf")
.Content
.PutAsync(fileStream);
Console.WriteLine($"File uploaded: {uploadedFile!.Name}, Size: {uploadedFile.Size} bytes");
Now I want you to notice something. There’s no SharePoint site URL anywhere in that code. No web-relative paths. No CAML queries. Just Graph, containers, and files. That’s the whole point.
Versioning and listing
Of course, storing files is only half the story. The real value of SharePoint has always been what it does after the file lands: versioning, metadata, audit trails. With SharePoint Embedded, all of that comes for free. Every file you upload is automatically versioned, just like it would be in a regular SharePoint document library. You can list the contents of a container and see exactly who modified what and when:
var items = await graphClient
.Storage
.FileStorage
.Containers[containerId]
.Drive
.Root
.Children
.GetAsync(config =>
{
config.QueryParameters.Select = new[]
{
"id", "name", "size", "lastModifiedDateTime", "lastModifiedBy"
};
config.QueryParameters.Orderby = new[] { "lastModifiedDateTime desc" };
});
foreach (var item in items!.Value!)
{
var modifiedBy = item.LastModifiedBy?.User?.DisplayName ?? "System";
Console.WriteLine($" {item.Name} | {item.Size} bytes | " +
$"Modified {item.LastModifiedDateTime:yyyy-MM-dd} by {modifiedBy}");
}
First off, if you’ve ever had to build versioning yourself on top of Azure Blob Storage, you know this is a massive win. I’ve been on projects where we spent weeks implementing version history, diff tracking, and rollback for documents stored in blobs. With SharePoint Embedded, you just get it. It’s SharePoint. It knows how to do this.
Permissions
Here’s where it gets interesting. You can set granular permissions on containers, giving specific users or groups access to specific content without them ever needing a SharePoint license for the UI:
await graphClient
.Storage
.FileStorage
.Containers[containerId]
.Permissions
.PostAsync(new Permission
{
Roles = new List<string> { "reader" },
GrantedToV2 = new SharePointIdentitySet
{
User = new Identity
{
Id = "user-object-id",
DisplayName = "External Reviewer"
}
}
});
This way you can have an external reviewer who can read contract documents through your app’s UI, with all the compliance and audit trails that SharePoint provides, without ever giving them access to your SharePoint tenant. That’s a scenario I’ve tried to build at least five times over the years, and it was never this clean.
How it compares
I know what you’re thinking. “Rick, can’t I just use Azure Blob Storage?” Sure. It’s cheap. It’s fast. But you get no versioning, no built-in permissions model, no search, no compliance features. You’ll end up building all of that yourself, and I promise you’ll underestimate how long it takes. I always do.
What about the OneDrive API? It’s great, but it’s tied to user context. You’re always operating within someone’s OneDrive, which doesn’t make sense for application-owned content like customer contracts or project documents. And if you use raw SharePoint through Graph, you still have the problem that your content lives in a SharePoint site that users can navigate to. Someone will find it, bookmark it, and start uploading random files outside your app’s workflow. I’ve seen it happen on every single project.
SharePoint Embedded gives you the storage engine of SharePoint (the versioning, the permissions, the compliance, the search) without the SharePoint UI layer. Your content lives in containers that are only accessible through your application.
Pricing
I’m not going to pretend this is free. Containers consume SharePoint storage, and that storage is billed to your Microsoft 365 tenant. You need to factor that into your architecture decisions. But here’s the honest calculation: for document-heavy applications that need enterprise features like versioning, granular permissions, eDiscovery holds, and retention policies, the alternative is building all of that yourself on top of blob storage. I’ve done that math before, and it doesn’t work out in favor of building it yourself. Not even close.
It took about 15 years, but Microsoft finally made SharePoint feel like a proper backend service. I’ll take it.