HomeAboutAll Posts

Vanilla JS and the SharePoint Graph API

By Denis Molodtsov
Published in SharePoint
June 01, 2026
3 min read
Vanilla JS and the SharePoint Graph API

Table Of Contents

01
The Problem
02
Why this matters for Power Automate
03
SharePoint speaks Graph natively — and your cookie is enough
04
The three equivalents
05
How to Use the Script
06
The Graph Explorer (split-pane tree)
07
Notes & gotchas
08
See also

The Problem

My other post shows how to explore SharePoint from the browser console with the classic _api/web REST API. But when you start building Power Automate flows that need to take advantage of the Microsoft Graph API, you quickly need the Graph identifiers instead: a library’s Drive ID (b!…), a file’s DriveItem ID, a list’s GUID, the composite Site ID.

Power Automate Fllow - Archive endpoint in Graph API
Power Automate Fllow - Archive endpoint in Graph API

Getting those usually means a detour through Graph Explorer (with an AAD token) or a PnP PowerShell session. There’s a faster way.

Why this matters for Power Automate

A huge amount of what you’d want to do with files and lists in Power Automate is only available — or only works cleanly — through the Microsoft Graph API: archiving a file, moving large items, reading drive metadata, hitting endpoints the standard SharePoint connector doesn’t expose. The catch is that almost every one of those Graph calls demands two cryptic identifiers up front: the Drive ID (the b!… blob that identifies a library) and the DriveItem ID (the file’s Graph id). Neither is shown anywhere in the SharePoint UI, and digging them out normally means Graph Explorer, an app registration, or a PowerShell script — a real wall for “common folk” who just want their flow to work.

This article tears that wall down. Paste one script into the browser console and you get a point-and-click tree of your site; click any file and the Drive ID, Item ID, and a ready-to-paste “Send an HTTP request to SharePoint” URL are right there with Copy buttons. No token, no app registration, no PowerShell.

Graph API in SharePoint
Graph API in SharePoint

Every SharePoint site exposes the Graph drive/site/list APIs locally, under /_api/v2.1/ (and the older /_api/v2.0/). Because the call is same-origin to the site you’re already signed in to, your session cookie authorizes it — no token, no app registration, no CORS. This is the same surface the “Send an HTTP request to SharePoint” action uses in Power Automate, which is why a flow can call:

_api/v2.1/drives/b!zHJ7_k3ARUGkdvulhQqTnI…X6F/items/<FILE_DRIVE_ID>/archive

…without any extra authentication. These scripts let you grab exactly those IDs and URLs from the comfort of the browser console.

Note: the responses are Graph-shaped — a value array, @odata.nextLink paging, camelCase properties — not the classic odata=verbose d.results envelope. The two API families live side by side.

The three equivalents

For every artifact, these scripts show you the same thing expressed three (sometimes four) ways, each with a one-click Copy button:

FlavorLooks likeGood for
Graph (global)https://graph.microsoft.com/v1.0/drives/{id}/items/{id}Graph SDKs, Graph Explorer, the “HTTP” connector
Graph (SharePoint-hosted)…/sites/MySite/_api/v2.1/drives/{id}/items/{id}“Send an HTTP request to SharePoint” — no token needed
SharePoint URLthe item’s webUrlopening it in the browser
REST API URL…/_api/web/getfilebyserverrelativeurl('…')the classic REST API / older scripts

Endpoint cheat-sheet these scripts rely on (all relative to the current site):

PurposeEndpoint
Current site (+ composite Site ID)GET /_api/v2.1/sites/root
Document libraries (drives)GET /_api/v2.1/drives
Items in a libraryGET /_api/v2.1/drives/{driveId}/root/children
Items in a folderGET /_api/v2.1/drives/{driveId}/items/{itemId}/children
Search inside a libraryGET /_api/v2.1/drives/{driveId}/root/search(q='…')
Lists on the siteGET /_api/v2.1/sites/root/lists
Items in a listGET /_api/v2.1/sites/root/lists/{listId}/items?$expand=fields

(/_api/v2.1/lists on its own returns apiNotFound — lists must be addressed through sites/root.)

How to Use the Script

  • Navigate to your SharePoint site
  • Open the browser console (F12)
  • Type allow pasting and press Enter (only needed once)
  • Copy the script below, paste it, press Enter
  • Enjoy the results

The Graph Explorer (split-pane tree)

A split-pane explorer: on the left, an expandable tree of the site’s libraries (drives) and lists; on the right, a detail panel for whatever you click, showing the three equivalents and the Power-Automate-ready IDs. Every detail view also has a Raw Graph JSON toggle that reveals the complete, pretty-printed Graph object for that node (with its own Copy button) — handy when you need a property the panel doesn’t surface, or want to see exactly what Graph returns.

Built for large lists and libraries. Nothing is loaded until you expand it, and each level pages in 200 items at a time behind a ⋯ Load more node driven by @odata.nextLink — so a folder with 5,000 files never freezes the page. Each library also has a 🔍 search box: type a term, press Enter, and it queries the whole library server-side (/root/search) instead of making you scroll. Click any node to see its IDs and URLs on the right; click Copy on the Drive ID + Item ID and you’re ready to paste into a flow.

javascript
(async function () {
async function getSPContext() {
const ctxAvailable = typeof _spPageContextInfo !== 'undefined' && _spPageContextInfo.webAbsoluteUrl;
let url = ctxAvailable ? _spPageContextInfo.webAbsoluteUrl : window.location.href.split('?')[0].split('#')[0];
if (!ctxAvailable) {
for (const s of ['/_layouts/','/_api/','/SitePages/','/Lists/','/Forms/','/Pages/','/SiteAssets/','/_app/']) {
const i = url.toLowerCase().indexOf(s.toLowerCase());
if (i > -1) { url = url.substring(0, i); break; }
}
url = url.replace(/\/[^\/]*\.aspx$/i, '');
}
const r = await fetch(`${url}/_api/contextinfo`, { method:'POST', headers:{'Accept':'application/json;odata=verbose'}, credentials:'same-origin' });
const c = (await r.json()).d.GetContextWebInformation;
return { webUrl: c.WebFullUrl, siteUrl: c.SiteFullUrl };
}
const ctx = await getSPContext();
const base = ctx.webUrl;
const G = async u => { const r = await fetch(u, { headers:{'Accept':'application/json'}, credentials:'same-origin' }); if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0,140)); return r.json(); };
const esc = s => (s==null?'':String(s)).replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
const fmtSize = n => n==null?'':n<1024?n+' B':n<1048576?(n/1024).toFixed(1)+' KB':(n/1048576).toFixed(1)+' MB';
const serverRel = (d, it) => { const root = decodeURIComponent(new URL(d.webUrl).pathname); const p = it.parentReference?.path||''; const rel = p.includes('root:') ? decodeURIComponent(p.split('root:')[1]||'') : ''; return root + rel + '/' + it.name; };
const site = await G(`${base}/_api/v2.1/sites/root`);
const siteId = site.id;
const drives = (await G(`${base}/_api/v2.1/drives?$top=200`)).value || [];
const lists = (await G(`${base}/_api/v2.1/sites/root/lists?$top=200`)).value || [];
// ---------- shell ----------
const wrap = document.createElement('div');
wrap.style.cssText = 'position:fixed;top:15px;left:50%;transform:translateX(-50%);width:96%;max-width:1600px;height:85vh;background:#fff;border:2px solid #0078d4;z-index:10000;box-shadow:0 6px 24px rgba(0,0,0,.2);display:flex;flex-direction:column;font-family:Segoe UI,Arial,sans-serif;user-select:text';
wrap.innerHTML = `
<div style="display:flex;align-items:center;gap:12px;padding:10px 16px;background:#0078d4;color:#fff">
<strong style="font-size:15px">🌐 SharePoint Graph Explorer</strong>
<span style="opacity:.9">${esc(site.title||site.name||'')}</span>
<code style="background:rgba(255,255,255,.2);padding:2px 6px;border-radius:3px;font-size:11px">_api/v2.1</code>
<button id="gxClose" style="margin-left:auto;padding:6px 14px;background:#d83b01;color:#fff;border:none;cursor:pointer;border-radius:3px">Close</button>
</div>
<div style="flex:1;display:flex;overflow:hidden">
<div id="gxTree" style="width:44%;min-width:340px;overflow:auto;border-right:1px solid #ddd;padding:8px 4px;font-size:13px"></div>
<div id="gxDetail" style="flex:1;overflow:auto;padding:16px 20px;font-size:13px;color:#222"></div>
</div>`;
document.body.appendChild(wrap);
const treeEl = wrap.querySelector('#gxTree');
const right = wrap.querySelector('#gxDetail');
wrap.querySelector('#gxClose').onclick = () => wrap.remove();
let selectedRow = null;
const select = row => { if (selectedRow) selectedRow.style.background=''; selectedRow = row; row.style.background='#cfe4fa'; };
// ---------- detail panel ----------
const BTN = 'padding:3px 10px;background:#107c10;color:#fff;border:none;border-radius:3px;cursor:pointer;font-size:11px;white-space:nowrap';
const copyBtn = text => { const b = document.createElement('button'); b.textContent='Copy'; b.style.cssText=BTN; b.onclick=()=>{ navigator.clipboard.writeText(text); b.textContent='Copied ✓'; setTimeout(()=>b.textContent='Copy',1200); }; return b; };
const header = (icon,name,kind) => { const d=document.createElement('div'); d.style.cssText='border-bottom:2px solid #0078d4;padding-bottom:8px;margin-bottom:12px'; d.innerHTML=`<div style="font-size:18px;font-weight:600">${icon} ${esc(name)}</div><div style="color:#888;font-size:12px;text-transform:uppercase;letter-spacing:.5px">${esc(kind)}</div>`; return d; };
const sect = t => { const d=document.createElement('div'); d.style.cssText='margin:16px 0 6px;font-size:12px;font-weight:700;color:#0078d4;text-transform:uppercase;letter-spacing:.5px'; d.textContent=t; return d; };
const metaGrid = pairs => { const d=document.createElement('div'); d.style.cssText='display:grid;grid-template-columns:auto 1fr;gap:4px 14px'; pairs.forEach(([k,v])=>{ if(v==null||v==='')return; const a=document.createElement('div'); a.style.cssText='color:#888'; a.textContent=k; const b=document.createElement('div'); b.style.wordBreak='break-all'; b.textContent=v; d.appendChild(a); d.appendChild(b); }); return d; };
const field = (label,value,hint) => { const d=document.createElement('div'); d.style.cssText='margin:8px 0'; const t=document.createElement('div'); t.style.cssText='font-size:11px;color:#666;margin-bottom:2px'; t.textContent=label+(hint?' — '+hint:''); const row=document.createElement('div'); row.style.cssText='display:flex;gap:8px;align-items:flex-start'; const code=document.createElement('code'); code.style.cssText='flex:1;word-break:break-all;background:#f6f8fa;padding:6px 8px;border-radius:4px;font-size:12px;border:1px solid #eaecef'; code.textContent=value; row.appendChild(code); row.appendChild(copyBtn(value)); d.appendChild(t); d.appendChild(row); return d; };
const detail = (...nodes) => { right.innerHTML=''; nodes.forEach(n => n && right.appendChild(n)); };
// collapsible, pretty-printed raw Graph object with its own Copy button
const rawJson = obj => {
const json = JSON.stringify(obj, null, 2);
const wrap = document.createElement('div'); wrap.style.cssText = 'margin-top:18px';
const bar = document.createElement('div'); bar.style.cssText = 'display:flex;gap:8px;align-items:center;margin-bottom:6px';
const title = document.createElement('span'); title.style.cssText = 'font-size:12px;font-weight:700;color:#0078d4;text-transform:uppercase;letter-spacing:.5px;margin-right:auto'; title.textContent = 'Raw Graph JSON';
const toggle = document.createElement('button'); toggle.style.cssText = 'padding:3px 10px;background:#0078d4;color:#fff;border:none;border-radius:3px;cursor:pointer;font-size:11px'; toggle.textContent = 'Show';
bar.appendChild(title); bar.appendChild(toggle); bar.appendChild(copyBtn(json));
const pre = document.createElement('pre'); pre.style.cssText = 'display:none;background:#1e1e1e;color:#d4d4d4;padding:12px;border-radius:6px;overflow:auto;max-height:440px;font-size:12px;line-height:1.5;white-space:pre;margin:0;tab-size:2';
pre.textContent = json;
toggle.onclick = () => { const show = pre.style.display === 'none'; pre.style.display = show ? 'block' : 'none'; toggle.textContent = show ? 'Hide' : 'Show'; };
wrap.appendChild(bar); wrap.appendChild(pre);
return wrap;
};
function showSite() {
detail(
header('🌐', site.title||site.name||'Site', 'Site'),
metaGrid([['URL',site.webUrl],['Template',site.template?.name],['Created',site.createdDateTime]]),
sect('IDs'),
field('Site ID (Graph composite)', siteId),
sect('Equivalent URLs'),
field('Graph (global)', `https://graph.microsoft.com/v1.0/sites/${siteId}`),
field('Graph (SharePoint-hosted)', `${base}/_api/v2.1/sites/root`),
field('SharePoint URL', site.webUrl),
field('REST API URL', `${base}/_api/web`),
rawJson(site)
);
}
function showDrive(d) {
const root = decodeURIComponent(new URL(d.webUrl).pathname);
detail(
header('🗂️', d.name, 'Document Library (drive)'),
metaGrid([['Type',d.driveType],['Modified',d.lastModifiedDateTime]]),
sect('IDs for Power Automate / Graph'),
field('Drive ID', d.id),
field('Send an HTTP request to SharePoint', `_api/v2.1/drives/${d.id}`, 'Uri'),
sect('Equivalent URLs'),
field('Graph (global)', `https://graph.microsoft.com/v1.0/drives/${d.id}`),
field('Graph (SharePoint-hosted)', `${base}/_api/v2.1/drives/${d.id}`),
field('SharePoint URL', d.webUrl),
field('REST API URL', `${base}/_api/web/getlist('${root}')`),
rawJson(d)
);
}
function showDriveItem(d, it) {
const driveId = it.parentReference?.driveId || d.id, id = it.id, isF = !!it.folder, sr = serverRel(d, it);
detail(
header(isF?'📁':'📄', it.name, isF?'Folder':'File'),
metaGrid([['Size', isF ? (it.folder.childCount+' items') : fmtSize(it.size)],['Modified',it.lastModifiedDateTime],['Modified by',it.lastModifiedBy?.user?.displayName],['MIME',it.file?.mimeType]]),
sect('IDs for Power Automate / Graph'),
field('Drive ID', driveId),
field('Item ID (DriveItem ID)', id),
field('Send an HTTP request to SharePoint', `_api/v2.1/drives/${driveId}/items/${id}`, 'Uri'),
sect('Equivalent URLs'),
field('Graph (global)', `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${id}`),
field('Graph (SharePoint-hosted)', `${base}/_api/v2.1/drives/${driveId}/items/${id}`),
field('SharePoint URL', it.webUrl),
field('REST API URL', `${base}/_api/web/${isF?'getfolderbyserverrelativeurl':'getfilebyserverrelativeurl'}('${sr}')`),
it['@content.downloadUrl'] ? field('Direct download URL', it['@content.downloadUrl']) : null,
rawJson(it)
);
}
function showList(l) {
detail(
header('📋', l.displayName||l.name, 'List · '+(l.list?.template||'')),
metaGrid([['Items',l.itemCount],['URL',l.webUrl]]),
sect('IDs'),
field('List ID', l.id),
sect('Equivalent URLs'),
field('Graph (global)', `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${l.id}`),
field('Graph (SharePoint-hosted)', `${base}/_api/v2.1/sites/root/lists/${l.id}`),
field('SharePoint URL', l.webUrl),
field('REST API URL', `${base}/_api/web/lists(guid'${l.id}')`),
rawJson(l)
);
}
function showListItem(l, it) {
const id = it.id, listId = it.parentReference?.listId || l.id, f = it.fields || {};
const shown = Object.keys(f).filter(k => !k.startsWith('@') && !k.startsWith('_')).slice(0,12).map(k => [k, typeof f[k]==='object' ? JSON.stringify(f[k]) : f[k]]);
detail(
header('📝', (f.Title||f.FileLeafRef||('Item '+id)), 'List item · '+(l.displayName||l.name)),
sect('Fields'), metaGrid(shown),
sect('IDs for Power Automate / Graph'),
field('List ID', listId),
field('Item ID', id),
field('Send an HTTP request to SharePoint', `_api/v2.1/sites/root/lists/${listId}/items/${id}`, 'Uri'),
sect('Equivalent URLs'),
field('Graph (global)', `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listId}/items/${id}`),
field('Graph (SharePoint-hosted)', `${base}/_api/v2.1/sites/root/lists/${listId}/items/${id}`),
field('SharePoint URL', it.webUrl),
field('REST API URL', `${base}/_api/web/lists(guid'${listId}')/items(${id})`),
rawJson(it)
);
}
// ---------- tree ----------
function createNode({ level, icon, label, meta, expandable, loadChildren, onSelect }) {
const w = document.createElement('div');
const row = document.createElement('div');
row.style.cssText = `display:flex;align-items:center;gap:4px;padding:3px 6px;cursor:pointer;border-radius:3px;padding-left:${6+level*16}px`;
row.onmouseenter = () => { if (row !== selectedRow) row.style.background='#f0f6fc'; };
row.onmouseleave = () => { if (row !== selectedRow) row.style.background=''; };
const tw = document.createElement('span'); tw.style.cssText='width:12px;flex:0 0 12px;color:#888;font-size:10px;text-align:center'; tw.textContent = expandable ? '▶' : '';
const lbl = document.createElement('span'); lbl.style.cssText='overflow:hidden;text-overflow:ellipsis;white-space:nowrap'; lbl.innerHTML = `${icon} ${esc(label)}` + (meta ? ` <span style="color:#999;font-size:11px">· ${esc(meta)}</span>` : '');
row.appendChild(tw); row.appendChild(lbl);
const kids = document.createElement('div'); kids.style.display = 'none';
w.appendChild(row); w.appendChild(kids);
let loaded = false, open = false;
row.addEventListener('click', async () => {
select(row); if (onSelect) onSelect();
if (!expandable) return;
open = !open; tw.textContent = open ? '▼' : '▶'; kids.style.display = open ? 'block' : 'none';
if (open && !loaded) {
loaded = true;
kids.innerHTML = `<div style="padding-left:${6+(level+1)*16}px;color:#0078d4;font-style:italic">Loading…</div>`;
try { await loadChildren(kids, level+1); }
catch (err) { kids.innerHTML = `<div style="color:#d83b01;padding-left:${6+(level+1)*16}px">${esc(err.message)}</div>`; }
}
});
return { w, kids, row };
}
// pages results into `container`, inserting before a reusable "Load more" node
function pager(container, level, firstUrl, renderItem) {
let next = firstUrl;
const more = document.createElement('div');
more.style.cssText = `padding:4px 6px;padding-left:${6+level*16}px;color:#0078d4;cursor:pointer;font-style:italic`;
more.textContent = '⋯ Load more';
const loadNext = async ev => {
if (ev && ev.stopPropagation) ev.stopPropagation();
more.textContent = 'Loading…';
try {
const data = await G(next);
(data.value || []).forEach(it => container.insertBefore(renderItem(it), more));
next = data['@odata.nextLink'] || null;
if (next) more.textContent = '⋯ Load more'; else more.remove();
} catch (err) { more.textContent = err.message; }
};
more.onclick = loadNext;
container.appendChild(more);
return loadNext;
}
function driveItemNode(level, d, it) {
const isF = !!it.folder;
return createNode({
level, icon: isF ? '📁' : '📄', label: it.name, meta: isF ? `${it.folder.childCount}` : fmtSize(it.size),
expandable: isF && it.folder.childCount > 0,
loadChildren: (kids, lv) => loadPaged(kids, lv, it2 => driveItemNode(lv, d, it2), `${base}/_api/v2.1/drives/${d.id}/items/${it.id}/children?$top=200&$expand=listItem`),
onSelect: () => showDriveItem(d, it)
}).w;
}
async function loadPaged(kids, level, render, firstUrl) {
kids.innerHTML = '';
const loadNext = pager(kids, level, firstUrl, render);
await loadNext();
}
async function loadDriveRoot(kids, level, d) {
kids.innerHTML = '';
const sRow = document.createElement('div'); sRow.style.cssText = `padding:4px 6px;padding-left:${6+level*16}px`;
const inp = document.createElement('input'); inp.placeholder = '🔍 search this library… (Enter)';
inp.style.cssText = 'padding:3px 6px;width:85%;font-size:12px;border:1px solid #ccc;border-radius:3px';
inp.onclick = e => e.stopPropagation();
sRow.appendChild(inp); kids.appendChild(sRow);
const listing = document.createElement('div'); kids.appendChild(listing);
const rootUrl = `${base}/_api/v2.1/drives/${d.id}/root/children?$top=200&$expand=listItem`;
const show = async url => { listing.innerHTML = ''; const loadNext = pager(listing, level, url, it => driveItemNode(level, d, it)); await loadNext(); };
await show(rootUrl);
inp.addEventListener('keydown', async e => {
if (e.key !== 'Enter') return;
const q = inp.value.trim();
if (!q) return show(rootUrl);
listing.innerHTML = `<div style="padding-left:${6+level*16}px;color:#0078d4;font-style:italic">Searching…</div>`;
try {
const data = await G(`${base}/_api/v2.1/drives/${d.id}/root/search(q='${encodeURIComponent(q)}')?$top=200`);
const v = data.value || []; listing.innerHTML = '';
if (!v.length) listing.innerHTML = `<div style="padding-left:${6+level*16}px;color:#666">No matches</div>`;
else v.forEach(it => listing.appendChild(driveItemNode(level, d, it)));
} catch (err) { listing.innerHTML = `<div style="padding-left:${6+level*16}px;color:#d83b01">Search unavailable: ${esc(err.message)}</div>`; }
});
}
function listItemNode(level, l, it) {
const f = it.fields || {}, title = f.Title || f.FileLeafRef || f.LinkTitle || ('Item ' + it.id);
return createNode({ level, icon:'📝', label:`#${it.id} ${title}`, expandable:false, onSelect:()=>showListItem(l, it) }).w;
}
// ---------- build roots ----------
const grp = t => { const d=document.createElement('div'); d.style.cssText='padding:8px 6px 3px;font-size:11px;font-weight:700;color:#888;text-transform:uppercase;letter-spacing:.5px'; d.textContent=t; return d; };
treeEl.appendChild(createNode({ level:0, icon:'🌐', label:site.title||site.name||'Site', meta:'site', expandable:false, onSelect:showSite }).w);
treeEl.appendChild(grp(`Libraries (${drives.length})`));
drives.forEach(d => treeEl.appendChild(createNode({ level:0, icon:'🗂️', label:d.name, meta:'library', expandable:true, loadChildren:(k,lv)=>loadDriveRoot(k,lv,d), onSelect:()=>showDrive(d) }).w));
treeEl.appendChild(grp(`Lists (${lists.length})`));
lists.forEach(l => treeEl.appendChild(createNode({ level:0, icon:'📋', label:l.displayName||l.name, meta:`${l.itemCount}`, expandable:l.itemCount>0, loadChildren:(k,lv)=>loadPaged(k,lv,it=>listItemNode(lv,l,it),`${base}/_api/v2.1/sites/root/lists/${l.id}/items?$expand=fields&$top=100`), onSelect:()=>showList(l) }).w));
showSite();
console.log('%cGraph Explorer ready','color:#0078d4;font-weight:bold');
console.table(drives.map(d => ({ name:d.name, driveId:d.id })));
})();

Results — the tree on the left mirrors your real libraries, lists, folders and files; click any node and every ID and equivalent URL is one click from your clipboard:

SharePoint Graph Explorer — file detail with Drive ID and Item ID
SharePoint Graph Explorer — file detail with Drive ID and Item ID

Notes & gotchas

  • No token needed because the call is same-origin — your SharePoint session cookie does the work. The exact same URLs (minus the host) go straight into Power Automate’s Send an HTTP request to SharePoint action.

See also

If you need the classic SharePoint REST API instead — listing fields, content types, subsites, permissions and navigation from the console — see the companion article: Vanilla JS and SharePoint REST API.


Tags

SharePointJavaScriptGraph APIPower Automate

Share

Previous Article
Archive SharePoint Files Using M365 Archive Feature
Denis Molodtsov

Denis Molodtsov

Microsoft 365 Architect

Related Posts

Archive SharePoint Online Sites Using M365 Archive Feature
Archive SharePoint Online Sites Using M365 Archive Feature
May 26, 2026
5 min

Quick Links

AboutAll Posts

Social Media