Four ways to update a term in the SharePoint Term Store programmatically — SharePoint’s own REST API, Microsoft Graph, plain JavaScript, and PnPjs — and what actually differs between them beyond “which one is easiest.”
In this post: Option 1: SharePoint REST API · Option 2: Microsoft Graph · Option 3: plain JavaScript · Option 4: PnPjs · Option 5: PnP PowerShell · The permission gotcha that hits every option · Which one should you actually use? · When would you actually use this? · Related reading
Option 1: SharePoint REST API
SharePoint’s own termStore REST endpoint works directly against your tenant, no other Microsoft 365 service involved:
fetch("https://yourtenant.sharepoint.com/sites/yoursite/_api/v2.1/termStore/groups/{groupId}/sets/{setId}/terms/{termId}", {
method: "PATCH",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": "Bearer your_access_token"
},
body: JSON.stringify({
"labels": [{ "name": "Updated Term Name", "languageTag": "en-US" }]
})
})
.then(response => response.json())
.then(data => console.log("Updated successfully", data))
.catch(error => console.error("Error updating term store", error));
Direct and dependency-free, but you’re handling OAuth/MSAL token acquisition yourself, and the auth setup is SharePoint-specific — it won’t carry over if you’re also touching other Microsoft 365 services in the same project.
Option 2: Microsoft Graph
Same operation through Graph’s unified endpoint instead:
fetch("https://graph.microsoft.com/v1.0/sites/{siteId}/termStore/sets/{setId}/terms/{termId}", {
method: "PATCH",
headers: {
"Authorization": "Bearer your_access_token",
"Content-Type": "application/json"
},
body: JSON.stringify({
"labels": [{ "name": "Updated Term Name", "languageTag": "en-US" }]
})
})
.then(response => response.json())
.then(data => console.log("Updated successfully", data))
.catch(error => console.error("Error updating term store", error));
Worth it specifically if the same app is already touching Outlook, Teams, or other Graph-covered services — one auth model instead of a separate one per API. Not worth switching to just for this one operation if SharePoint REST already works for the rest of your project.
Option 3: plain JavaScript
Functionally the same as Option 1, wrapped in an async function for cleaner error handling — useful if you want this as a reusable utility rather than an inline call:
async function updateTerm(termId, groupId, setId, accessToken) {
const requestUrl = `https://yourtenant.sharepoint.com/sites/yoursite/_api/v2.1/termStore/groups/${groupId}/sets/${setId}/terms/${termId}`;
const headers = new Headers({
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": `Bearer ${accessToken}`
});
const body = JSON.stringify({
"labels": [{ "name": "Updated Term Name", "languageTag": "en-US" }]
});
try {
const response = await fetch(requestUrl, { method: "PATCH", headers, body });
const data = await response.json();
console.log("Updated successfully", data);
return data;
} catch (error) {
console.error("Error updating term store", error);
throw error;
}
}
Option 4: PnPjs
PnPjs’s taxonomy module wraps term store access behind a typed, promise-based API instead of hand-built REST calls. Reading term store data is well-documented and stable:
import { spfi } from "@pnp/sp";
import "@pnp/sp/taxonomy";
const sp = spfi(/* your configured instance */);
// Get term store info, then navigate down to a set
const termStoreInfo = await sp.termStore();
const termSet = sp.termStore.groups.getById(groupId).sets.getById(setId);
const terms = await termSet.getAllTerms();
Worth flagging directly rather than guessing: PnPjs’s taxonomy module is explicitly called out in PnP’s own docs as newer and still evolving compared to the rest of the library, and update/write operations on terms aren’t as consistently documented as the read side shown above. If you’re relying on PnPjs specifically to write term updates in production, check the current PnPjs taxonomy documentation for the exact method signature rather than assuming it matches an older example — this is one corner of the library where copying a two-year-old snippet is more likely to be wrong than usual.
Option 5: PnP PowerShell
Worth including as a fifth option, since it’s genuinely the most common real-world way this gets done outside of an application actually running in production — a one-off bulk rename, a naming-convention cleanup, or a migration script doesn’t need a web app wrapped around it:
Connect-PnPOnline -Url "https://yourtenant.sharepoint.com/sites/yoursite" -Interactive
Set-PnPTerm -Identity "OldTermName" -TermGroup "YourTermGroup" -TermSet "YourTermSet" -Name "Updated Term Name"
Worth distinguishing this from a separate, already-covered topic on this site: this updates the term itself (its name, description, or properties) — tagging an existing list item with a term it doesn’t currently have is a different operation entirely, covered in this site’s SharePoint Term Store PowerShell content. Confusing the two is a common source of “why didn’t my script do anything” when a term-tagging approach is applied to what’s actually a rename, or vice versa.
The permission gotcha that hits every option
All four approaches above will 403 on a write, no matter how correctly the code is written, if the calling identity isn’t actually authorized against the term store itself — and that authorization is separate from the app permissions/scopes each option needs. For Graph specifically, TermStore.ReadWrite.All only works as a delegated permission (a signed-in user), not as an application/app-only permission — Graph’s term store API doesn’t support pure app-only writes the way many other Graph endpoints do. And for an app-only identity attempting writes through any of the approaches above, admin consent on the Graph/SharePoint permission alone isn’t sufficient: the app’s identity also has to be explicitly added as a Term Store Administrator (or a Group Manager on the specific term group being edited) inside the Taxonomy Term Store admin UI itself. Skipping that second step is the single most common reason “the permissions look right but writes still fail” here.
Which one should you actually use?
| Approach | Ease of use | Dependencies | Best for |
|---|---|---|---|
| SharePoint REST API | Medium | None | SharePoint-only projects, no other M365 integration |
| Microsoft Graph | Medium | Graph permissions in Entra ID | Projects already touching other M365 services |
| Plain JavaScript | Hard | None | A reusable utility function, no framework |
| PnPjs | Easy (reads); check docs (writes) | PnPjs library | Larger SPFx projects already using PnPjs elsewhere |
| PnP PowerShell | Easy | PnP.PowerShell module | One-off bulk renames, migrations, admin scripts |
Microsoft Graph is worth it specifically if the same app is already touching other Microsoft 365 services — not worth switching to for this one operation alone.
When would you actually use this?
- A term set’s labels need to change in bulk (a naming convention update, a rebrand) — scripting it beats editing terms one at a time in the admin center.
- You’re building an SPFx web part that already uses PnPjs elsewhere in the project — staying consistent with one library beats mixing in raw REST calls for just this one feature.
- You’re integrating term store updates into a larger Microsoft 365 automation that also touches Teams or Outlook — that’s when Graph’s unified auth model actually pays off.
Related reading
- SharePoint Metadata: What You Need to Know — the broader picture of how metadata, content types, and the Term Store fit together.
- SharePoint Metadata Naming Convention — worth reading before you set up a term set, since internal names lock in at creation the same way column names do.
- SharePoint Term Store with PowerShell and JavaScript — tagging list items with an existing term, a genuinely different operation from updating the term itself.
That covers all four approaches. Questions about your specific setup? Drop them in the comments.
App Catalog Authentication Automation Backup Compliance Content Type CSS Flows Google Javascript Limitations List Metadata MFA Microsoft Node NodeJs O365 OneDrive Permissions PnP PnPJS Policy PowerApps Power Automate PowerAutomate PowerPlatform PowerShell React ReactJs Rest API Rest Endpoint Security Send an HTTP Request to SharePoint SharePoint SharePoint List SharePoint Modern SharePoint Online SPFX SPO Sync Tags Teams Termstore Versioning


