SharePoint Embedded at Scale: Multi-Tenant Architecture Patterns for ISVs
A SharePoint Embedded pilot is deceptively easy to stand up. You register an application in Entra ID, create a container type, provision a handful of containers, and suddenly your product can embed Microsoft 365 document editing inside its own interface with full fidelity. The trouble starts when that pilot succeeds. The patterns that carried you through a proof of concept — a single container type, manual consent, ad hoc Graph calls — quietly become liabilities once you are serving hundreds or thousands of customer tenants. This article is about designing a SharePoint Embedded multi-tenant architecture that holds up under that load, and about the operational realities of SharePoint Embedded for ISVs at scale that only become visible once you are past the demo.
We assume you already know what SharePoint Embedded is: a headless, container-based document store built on the SharePoint platform, owned and billed to your application’s tenant rather than to your customers. What follows is the next layer of the problem — how to partition data, manage container type lifecycle, onboard consuming tenants automatically, survive Microsoft Graph throttling, attribute consumption, distribute data geographically, and observe the whole estate.
Two Tenancy Models, and the One You Actually Have
Before any architecture decisions, it is worth being precise about the tenancy boundaries in play, because SharePoint Embedded has three distinct tenant roles and they are easy to conflate.
- The owning (provider) tenant is your tenant. You register the application, you own the container type, and Microsoft bills the storage and consumption to you.
- The consuming tenant is each customer’s Microsoft 365 tenant. Containers physically live in the consuming tenant’s SharePoint, even though your application controls them.
- The application tenant model itself — whether your Entra ID app is single-tenant or multi-tenant — governs how customers grant you access.
For an ISV, the application is almost always a multi-tenant Entra ID app registration, and each customer’s Microsoft 365 tenant is a separate consuming tenant. This is the configuration this article addresses. It is distinct from the scenario where you operate containers entirely within your own tenant on behalf of customers who have no Microsoft 365 of their own — a valid model, but one with very different residency and billing consequences that we touch on later.
Container Strategy: One Container Per Customer, Shared Containers, or Both
The first architectural fork is how you map customers onto containers. There is no universally correct answer, but there is a correct way to reason about it.
A container per customer (or per workspace)
The cleanest mental model is a container per customer tenant, or more often a container per logical workspace within a customer (a project, a matter, a client engagement). Containers are the natural security and isolation boundary in SharePoint Embedded: permissions, sharing, and Graph-level access control all attach to the container. A container-per-workspace model gives you:
- Hard isolation. A permissions bug cannot leak documents across customers because the blast radius stops at the container.
- Clean offboarding. Deleting or exporting a customer is
DELETE /storage/fileStorage/containers/{id}plus a recycle-bin purge, not a query-and-filter exercise across shared storage. - Per-container metering. Storage and column data are reported at container granularity, which makes billing attribution tractable (more on this below).
The cost is volume. SharePoint Embedded supports very large numbers of containers per container type, but you must treat container count as a first-class capacity dimension and design enumeration, listing, and reporting so they never depend on fetching all containers synchronously.
Shared containers with partitioning
The alternative is fewer, larger containers partitioned internally — by folder hierarchy and by custom columns on the driveItem metadata. This reduces container sprawl and can simplify some cross-customer search scenarios, but it pushes the isolation burden up into your application logic. Every read and write must enforce the partition key, and a single coding error becomes a cross-tenant data exposure rather than a contained one. For most ISVs subject to customer security review, that trade is not worth it.
The pragmatic hybrid
In practice many production designs are hybrid: a container per customer workspace for primary document storage, plus a small number of shared, application-owned containers for templates, shared assets, and system artefacts that genuinely belong to your service rather than to any one customer. The rule of thumb that has held up across McKenna engagements is simple — customer data gets its own container; your data can share one.
Container Type Lifecycle Across a Growing Customer Base
The container type is the schema and governance object that sits above every container you create. At pilot scale you create one and forget it. At scale, the container type becomes a versioned contract you have to manage deliberately.
Trial versus billed container types
You will typically begin with a trial container type, which is free, time-boxed, and capped at a small number of containers. Moving to production means registering a billed container type linked to an Azure subscription for consumption billing. Plan this migration early: trial containers cannot simply be reclassified, and discovering the cap during a customer rollout is a painful way to learn it. Treat the trial-to-billed transition as a scheduled milestone with its own runbook, not as an afterthought.
Configuration and the registration handshake
Two steps are easy to forget at scale. First, the container type’s owning application and its permissions are defined on the type itself. Second — and this trips up almost everyone — the container type must be registered in each consuming tenant via the SharePoint REST endpoint (/_api/v2.1/storageContainerTypes/{containerTypeId}/applicationPermissions) before your application can create containers there. This registration is per consuming tenant and is a hard prerequisite. Automating it is central to onboarding, which we come to next.
Evolving the schema
Custom columns, document metadata, and container properties will evolve as your product does. Because containers are provisioned over time, you will always have a population of containers created against older configurations. Build a reconciliation job that can walk the estate and bring containers up to the current schema version, and version-stamp every container with a custom property at creation so you know exactly what each one was provisioned against. Without this, schema drift across thousands of containers becomes impossible to reason about.
Automating Consuming-Tenant Onboarding
At pilot scale, onboarding a customer is a person clicking through an admin consent prompt. At scale it is a self-service flow that must complete in seconds with no human in the loop on your side. A robust onboarding pipeline has four stages.
- Admin consent. The customer’s Microsoft 365 administrator grants your multi-tenant application the Graph permissions it needs —
FileStorageContainer.Selectedat minimum, plus the application permission for your specific container type. Drive this through the admin consent endpoint and capture the resulting service principal in the consuming tenant. - Container type registration. Immediately after consent, call the storage container type registration endpoint in the consuming tenant to register your container type and its application permissions. This is the step that cannot be skipped and is the single most common cause of “it worked in the pilot tenant but not for the new customer”.
- Initial provisioning. Create the customer’s first container(s), stamp them with your schema version and a tenant identifier custom property, and seed any default folder structure or templates.
- Verification and event wiring. Confirm read/write access with a synthetic operation, and subscribe to change notifications (Microsoft Graph webhooks) on the new containers or drives so your downstream indexing and audit systems are wired from day one.
Make every stage idempotent and resumable. Consent can succeed while registration fails on a transient error; provisioning can partially complete. A queue-driven state machine that records where each tenant is in the onboarding flow — and can re-enter at the failed step — is far more reliable than a single long synchronous request, and it is what lets onboarding scale without operator intervention.
Designing Around Microsoft Graph Throttling and Service Limits
This is the section that separates a pilot from a production service. Every SharePoint Embedded operation — container CRUD, file upload, permission change, metadata update — flows through Microsoft Graph, and Graph throttles aggressively. At scale you will hit limits, and the question is whether your architecture absorbs them gracefully or falls over.
Respect 429 and Retry-After
When Graph throttles you it returns HTTP 429 (or sometimes 503) with a Retry-After header. The non-negotiable baseline is to honour Retry-After exactly, then apply exponential backoff with jitter on top. Never busy-retry a 429; that simply deepens the throttle and can escalate you to longer lockouts. Centralise this in a single Graph client wrapper so that every call site inherits correct backoff behaviour rather than each developer reinventing it.
Limits apply across multiple dimensions
Throttling is not a single global tap. Limits apply per application, per consuming tenant, per resource, and against SharePoint’s own service-level thresholds beneath Graph. A burst of provisioning for one large customer can starve unrelated customers if all traffic shares one undifferentiated queue. The design response is to partition outbound Graph work per consuming tenant and rate-limit each partition independently, so one noisy tenant cannot consume the whole budget.
Batch, deduplicate, and prefer events over polling
Use Graph $batch to combine related operations and cut request count. Coalesce redundant work — if three user actions all touch the same container property, collapse them into one update. And critically, do not poll for changes. Use Microsoft Graph change notifications (webhooks) with subscriptions on the relevant drives and containers, falling back to delta queries for catch-up after downtime. Polling at multi-tenant scale is the fastest route to self-inflicted throttling.
Token and connection hygiene
Cache and reuse access tokens per consuming tenant rather than requesting fresh tokens per call, and refresh proactively before expiry. Token endpoints throttle too, and a thundering herd of token requests during a deployment can take you down just as surely as data-plane throttling.
Billing Attribution and Metering Consumption
When Microsoft bills storage and consumption to your owning tenant, you receive one aggregate figure. Attributing that cost back to individual customers — for cost recovery, margin analysis, or usage-based pricing — is your responsibility, and it is far easier if you design for it from the start.
The container-per-customer model pays off here. Storage consumed is reported at container granularity, so a periodic job that reads each container’s storage metrics, tagged with the tenant identifier you stamped at provisioning time, produces a clean per-customer consumption ledger. Record point-in-time snapshots on a fixed cadence so you can show usage over a billing period rather than a single instantaneous reading.
For shared containers, attribution requires you to meter at the application layer — counting bytes and operations per partition key as you write them — because the platform cannot disaggregate what it sees as one container. This is another reason the per-customer container model tends to win: the platform does the metering work you would otherwise have to build and reconcile yourself.
Reconcile your internal ledger against the actual Azure consumption bill monthly. Discrepancies usually point to orphaned containers from failed offboarding, or to system overhead you are not yet accounting for — both of which are findings worth catching early.
Regional Distribution and Data Residency
Enterprise customers will ask where their documents physically reside, and for regulated sectors the answer is contractual. SharePoint Embedded inherits SharePoint’s data residency model, which means residency follows the consuming tenant’s home geography. Because containers live in the consuming tenant’s SharePoint, a customer whose Microsoft 365 tenant is provisioned in the UK or EU geography has their container data resident there by virtue of that tenant placement.
Where this gets involved is Multi-Geo. A single large customer may have a Multi-Geo configuration with data distributed across several satellite geographies, and your provisioning logic must place containers in the correct geography for each user or workload rather than defaulting to the tenant’s central location. Capture the required geography as part of onboarding, validate it against what the customer’s tenant actually supports, and stamp it on the container so your residency reporting can prove placement after the fact.
If instead you operate the alternative model — containers in your own tenant on behalf of customers — then residency is governed by your tenant’s geography, and serving customers in multiple regions means operating separate provider tenants or a Multi-Geo configuration of your own. That is a significant operational commitment and should be a deliberate, documented decision rather than something you back into.
Observability Across a Multi-Tenant Estate
You cannot operate what you cannot see, and a multi-tenant estate fails in ways a single-tenant pilot never does — one tenant throttled while others are fine, one customer’s containers drifting from the current schema, one region degraded.
Effective observability rests on three pillars. Correlate every Graph operation with a consuming-tenant identifier and a container identifier in your logs and traces, so you can slice latency, error rate, and throttling by customer rather than only in aggregate. Track throttling as a first-class metric, with 429 rate broken down per tenant and per operation type, because a rising 429 rate for one tenant is an early-warning signal long before customers notice. And maintain an estate inventory — a continuously refreshed view of every container, its owning tenant, its schema version, its geography, and its last-known health — built from change notifications and periodic reconciliation rather than live enumeration.
Layer on top of this the Microsoft 365 audit log and SharePoint Embedded’s own activity signals for security and compliance reporting, and feed Graph throttling metrics into capacity planning so growth is something you forecast rather than something that surprises you.
A Reference Architecture, and the Runbook Items That Come With It
Pulling the threads together, a production reference architecture for SharePoint Embedded at scale has these components:
- A multi-tenant Entra ID application with least-privilege Graph permissions and a billed container type linked to an Azure subscription.
- An onboarding service implemented as an idempotent, queue-driven state machine handling consent, container type registration, provisioning, and event wiring.
- A Graph gateway — a single client layer enforcing per-tenant rate limiting,
Retry-After-aware backoff, batching, and token caching, so no application code talks to Graph directly. - An event ingestion pipeline consuming Graph change notifications with delta-query fallback, feeding indexing, audit, and metering.
- A metering and billing service producing a per-tenant consumption ledger reconciled monthly against Azure.
- An estate inventory and observability plane with per-tenant correlation, throttling metrics, and schema-drift detection.
The operational runbook that surrounds this architecture matters as much as the architecture itself. The items that only become visible at scale include:
- Capacity planning. Track container count, storage growth, and per-tenant Graph request volume against service limits, and forecast headroom before each major customer onboarding rather than after.
- Tenant offboarding. A repeatable, auditable sequence to export or delete a departing customer’s containers, purge recycle bins, revoke the consuming-tenant registration, and remove the tenant from billing and inventory — leaving no orphaned storage to inflate your bill.
- Incident isolation. The ability to identify, rate-limit, or quarantine a single misbehaving tenant without degrading the rest of the estate. This is only possible if your Graph gateway partitions work per tenant from the outset.
- Schema reconciliation. A scheduled walk of the estate to bring older containers up to the current container type configuration, with progress tracked against the version stamps you applied at provisioning.
- Throttling and degradation drills. Periodic exercises that confirm your backoff and queuing behave correctly under sustained 429 conditions, before a real surge proves they do not.
How McKenna Consultants Can Help
Standing up a SharePoint Embedded pilot proves the concept; running it for hundreds or thousands of customers proves the engineering. The hard parts — idempotent multi-tenant onboarding, a Graph gateway that survives throttling, per-customer billing attribution, data residency you can evidence, and observability across the whole estate — are precisely the areas where a well-designed SharePoint Embedded multi-tenant architecture pays for itself and where a rushed one becomes a liability.
McKenna Consultants has spent more than 25 years building Microsoft document integration, from custom WOPI hosts to production SharePoint Embedded estates, and our engagements focus on getting these scale concerns right before they become incidents. If you have a SharePoint Embedded pilot that needs to become a scalable product — or an estate already in production that is straining against Graph limits, billing complexity, or residency requirements — we would be glad to help. Get in touch to talk through your architecture.