SharePoint Site Scripts: The Complete Provisioning Guide (2026)
A site script is a JSON file that automates SharePoint provisioning: lists, content types, theming. Verb reference, PnP PowerShell, and tenant limits inside.

A site script is a JSON file that lists, in order, the actions SharePoint should run against a site — create a library, add a content type, apply a theme, join a hub. Attach it to a site template and it runs the moment someone clicks "Create site," or apply it directly to a site that already exists (Microsoft Learn: SharePoint site template and site script overview).
Key Takeaways
- A site script is a JSON document; each step in it is an object with averbproperty —createSPList,createContentType,applyTheme,joinHubSite, and more.
- Site templates were called site designs — Microsoft renamed the outer wrapper, but the underlying site script JSON format didn't change.
- A script applied synchronously caps out at 30 actions (subactions included); applied asynchronously, the ceiling is 300 actions or 100,000 characters.
- You can run a site script two ways: attach it to a template so it fires on new-site creation, or invoke it directly against a site that already exists.
- PnP PowerShell (Add-PnPSiteScript,Invoke-PnPSiteScript) is the fastest path from JSON file to running script — no site template step required for existing sites.
- Site scripts are a SharePoint Online-only feature; there's no on-premises equivalent.
- Malformed JSON is the single most common failure — a missing comma or wrong verb name breaks the whole action list, not just the offending step.
---
What Is a SharePoint Site Script?
A site script is a declarative, JSON-based description of site changes — no code, no deployment package, just an ordered actions array that SharePoint executes top to bottom. Each entry names a verb — the operation to run — plus whatever parameters that verb needs.
Two things make site scripts worth knowing over writing a provisioning app from scratch:
- No custom code to deploy or maintain. The script is JSON, stored in the tenant's site script store. Update the JSON, the behavior updates — nothing to package, nothing to redeploy.
- Works against new and existing sites. Attach a script to a site template and Microsoft 365 offers it in the "Create site" flow. Or skip the template entirely and run
Invoke-PnPSiteScriptagainst a site that's already live — useful for retrofitting standards onto sites that predate your provisioning process.
If you'd rather not hand-write the action JSON, the Site Script Generator on this site builds it visually and outputs valid JSON you can paste straight into Add-PnPSiteScript.
---
Prerequisites
- A SharePoint Online tenant — site scripts don't exist on SharePoint Server or SharePoint on-premises.
- PnP PowerShell installed (
Install-Module PnP.PowerShell) if you're scripting from the command line rather than the SharePoint admin UI.
- SharePoint admin or Site Owner permissions on the target site, and (for tenant-wide script/template management) SharePoint Administrator role in Microsoft 365.
- A text editor with JSON linting — a stray comma is the most common reason a script fails to apply, and catching it before upload saves a debug cycle.
---
Writing a Site Script: A Working Example
A site script that defines a site column, wraps it in a content type, creates a document library that uses both, and applies a theme:
{
"$schema": "https://developer.microsoft.com/json-schemas/sp/site-design-script-actions.schema.json",
"actions": [
{
"verb": "createSiteColumn",
"fieldType": "Text",
"displayName": "Project Code",
"internalName": "ProjectCode",
"isRequired": true
},
{
"verb": "createContentType",
"name": "Project Documents",
"description": "Content type for project-related documents",
"parentName": "Document",
"subactions": [
{
"verb": "addSiteColumn",
"internalName": "ProjectCode"
}
]
},
{
"verb": "createSPList",
"listName": "Project Deliverables",
"templateType": 101,
"subactions": [
{
"verb": "addContentType",
"name": "Project Documents"
},
{
"verb": "addSPField",
"internalName": "DueDate",
"displayName": "Delivery Date",
"fieldType": "DateTime",
"isRequired": false
}
]
},
{
"verb": "applyTheme",
"themeName": "Blue Marble"
}
]
}Every action is a verb plus its parameters. Actions that need to happen inside another action — attaching a site column to the content type you just defined, say — go under subactions rather than as a new top-level entry (Microsoft Learn: site template JSON schema). Get the nesting wrong and SharePoint will usually still accept the upload; it just won't do what you expected at run time, which is a more annoying failure mode than a flat-out rejection.
Note the two ways a field enters a list here. createSiteColumn defines a reusable site column that any content type can pull in via addSiteColumn. addSPField on createSPList, by contrast, defines a field local to that one list. Reach for a site column when other lists or content types will want the same field later; use addSPField when it's a one-off.
Hub Site Association
Register a provisioned site with a hub the moment it's created, so it inherits navigation and governance without a separate manual step:
{
"verb": "joinHubSite",
"hubSiteId": "{your-hub-site-id}",
"name": "Sales Hub"
}This matters most in multi-tenant or department-heavy setups, where dozens of sites need to land under the same hub without someone remembering to click "Associate with a hub site" by hand every time.
---
Common Site Script Verbs
You won't use every verb in the schema on a given script, but these are the ones that show up in most real provisioning scripts:
| Verb | What it does |
|---|---|
createSPList | Creates a list or library; takes subactions for its fields, views, and content types |
createContentType | Defines a new content type at the site level |
addContentType (subaction) | Attaches an existing or just-defined content type to the list being built |
addSPField (subaction) | Adds a field to a list using the simplified field syntax |
addSPFieldXml (subaction) | Adds a field defined via raw field XML, for anything the simplified syntax can't express |
applyTheme | Applies a theme already registered in the tenant |
setSiteLogo | Sets the site's logo image |
joinHubSite | Associates the site with an existing hub |
installSolution | Installs a deployed SPFx solution package on the site |
triggerFlow | Calls a Power Automate flow — the standard hook for chaining downstream automation onto provisioning |
setRegionalSettings | Sets locale, time zone, and calendar type |
addNavLink | Adds a link to site navigation |
Full parameter lists for every verb live in the site template JSON schema — treat this table as the shortlist, not the whole reference.
---
Site Script and Site Template Limits
Two ceilings matter once you're running these at scale. A script applied synchronously caps out at 30 total actions, subactions included. Applied asynchronously — the path most current tooling uses — that ceiling rises to 300 actions, or 100,000 characters, whichever comes first (SharePoint dev docs: site template and site script overview, Microsoft). Per tenant, the store separately holds up to 100 site scripts and 100 site templates before it's full.
Cross either action ceiling and the script still uploads without complaint. It just hangs on the "Initializing" dialog the moment someone tries to create a site from it — a confusing failure to debug blind if you don't already know the limit exists.
---
Running a Site Script with PnP PowerShell
PnP PowerShell is the fastest way to go from a JSON file to a running script — you don't need a site template step at all if you're targeting one existing site.
Upload the script to the tenant's site script store:
Connect-PnPOnline -Url "https://contoso.sharepoint.com" -Interactive$scriptContent = Get-Content -Path ".\project-site-script.json" -Raw
Add-PnPSiteScript -Title "Project Site Provisioning" -Description "Adds project library, content type, theme" -Content $scriptContent
Add-PnPSiteScript returns the new script's Id — you'll need it if you're attaching the script to a site template with Add-PnPSiteDesign. But if you just want to apply it to a site right now, skip the template step entirely:
Invoke-PnPSiteScript -Script $scriptContent -WebUrl "https://contoso.sharepoint.com/sites/project-alpha"Invoke-PnPSiteScript accepts the raw JSON directly — the script doesn't have to exist in the site script store first (Microsoft Learn: SharePoint site design PnP PowerShell cmdlets). That's the difference that matters day to day. Use Add-PnPSiteScript when the script needs to be reusable across many future site creations via a template. Go straight to Invoke-PnPSiteScript when you're just fixing up one site right now.
To see what scripts already exist in the tenant before you add a duplicate:
Get-PnPSiteScript | Select-Object Id, TitleWriting repetitive PnP scripts by hand doesn't scale past the second or third project site. The PnP PowerShell Generator configures common provisioning requirements visually and outputs the exact script to run.
---
Provisioning via the REST API
Some scenarios call for creating resources from a custom application, a Power Automate flow, or an Azure Function rather than a signed-in admin's PowerShell session. The SharePoint REST API exposes the same list- and site-creation operations over HTTP (Microsoft Learn: SharePoint site design REST API).
Create a list via a POST request:
const siteUrl = 'https://yourtenant.sharepoint.com/sites/marketing';
const listTitle = 'Campaign Assets';const response = await fetch(${siteUrl}/_api/web/lists, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-RequestDigest': await getRequestDigest(siteUrl)
},
body: JSON.stringify({
__metadata: { type: 'SP.List' },
Title: listTitle,
BaseTemplate: 101, // Document Library
Description: 'Centralized storage for campaign creative assets'
})
});
const list = await response.json();
console.log(List created with ID: ${list.Id});
A typical enterprise flow chains all three approaches together instead of picking one:
- A requester submits a "New Project Site" form through a Power Automate portal page.
- A flow validates the request and checks naming conventions.
- The site is created from a template whose site script builds the standard libraries, content types, and branding.
- Follow-up REST calls (still from Power Automate) wire up downstream integrations — syncing metadata to Dataverse, kicking off onboarding tasks, whatever the process needs next.
Microsoft's own tutorial on triggering Power Automate from a site design walks through wiring a flow to fire on script completion, which is the piece that turns a one-off script into an actual self-service pipeline.
If you're building the request-intake side of that flow, the REST API Builder helps construct the endpoint URIs without memorizing every _api path. And once the site exists, querying the lists it just created is a CAML problem — the CAML Query Builder generates the XML filters for that.
---
Common Errors and Fixes
The site script failed validation.Cause: malformed JSON — usually a trailing comma, a misspelled verb, or an action nested at the wrong level.
Fix: validate the JSON with a linter before uploading; check the exact verb spelling against the verb table above rather than guessing from memory.
Access denied when calling Add-PnPSiteScriptCause: the connected account lacks SharePoint Administrator rights at the tenant level.
Fix: reconnect with an account that holds the SharePoint Administrator role, or have an admin run the upload.
- Script runs but nothing visible changes — check you targeted the right
-WebUrl;Invoke-PnPSiteScriptsilently no-ops actions that don't apply to the target site's current state (for example,createSPListagainst a list name that already exists).
- Theme doesn't apply —
applyThemerequires the theme name to match one already registered in the tenant; custom themes need registering withAdd-PnPThemefirst.
- Hub association silently fails — confirm the
hubSiteIdis the hub site's actual site ID, not its URL; a URL in that field is accepted by the JSON schema but won't resolve to a hub.
---
Frequently Asked Questions
What's the difference between a site script and a site template?
A site script is the JSON action list; a site template (formerly called a site design) is the wrapper that makes a script available in the "Create site" UI and can bundle multiple scripts together. You can run a site script without ever creating a template, by invoking it directly against an existing site.
Do site scripts work with SharePoint Server on-premises?
No. Site scripts and site templates are a SharePoint Online-only capability. On-premises provisioning still relies on PnP provisioning templates or custom code.
Can I apply a site script to a site that already exists?
Yes — Invoke-PnPSiteScript runs a script against any site you target, whether or not that site was created from a template. This is the standard way to retrofit standard libraries, content types, or branding onto sites that predate your provisioning process.
What happens if an action in the script fails partway through?
Actions run in the order listed, and a failure in one action doesn't automatically roll back actions that already succeeded. That's why validating JSON and testing against a throwaway site first matters more than it would with a fully transactional operation.
Can Power Automate trigger a site script automatically?
Yes — add a triggerFlow action to the script, typically as the last step, and it calls a Power Automate flow during provisioning. That's the standard way to chain downstream automation — Dataverse sync, approval routing, notifications — onto a site script instead of bolting it on afterward.
How do I find site scripts that already exist in my tenant?
Run Get-PnPSiteScript to list every script registered in the site script store. Or run Get-PnPSiteScriptFromWeb -WebUrl to reverse-engineer a script from a site's current configuration instead — useful when you want to replicate an existing site's setup rather than write the JSON from scratch.
---
That's the full loop: write the site script's JSON, watch the action and character ceilings if you're provisioning at scale, then ship it through PnP PowerShell or REST depending on where the request comes from. Whether you hand-write the JSON or generate it, the same verb reference and limits apply either way.
What's Next
- PnP PowerShell for SharePoint Online: Admin Scripts Guide (2026) — the broader PnP PowerShell toolkit beyond site scripts
- CAML Query for SharePoint Lists: The Developer Guide (2026) — querying the lists your site script just created
- Microsoft Graph Sites.Selected: Granular SharePoint Permissions (2026) — scoping app access to provisioned sites
- SharePoint Embedded: The Complete Developer Guide (2026) — when a full SharePoint site is more than the scenario needs
- Site Script Generator tool — build schema-valid site script JSON visually
- PnP Script Generator tool — generate PnP PowerShell provisioning scripts without starting from a blank file