HomeAboutAll Posts

Vanilla JS and SharePoint REST API

By Denis Molodtsov
Published in SharePoint
June 30, 2025
2 min read
Vanilla JS and SharePoint REST API

Table Of Contents

01
The Problem
02
Solution: JavaScript in the Browser Console for a Quick Report
03
How to Use the Scripts
04
Get List of Lists and Content Types
05
Get the List of Lists and Libraries. Advanced Version.
06
Get Columns for Lists and Libraries
07
Get All Webs in a Site Collection
08
Get All Web Permissions in Site Collection
09
But Denis, why not build a Chrome extension?
10
Conclusion

The Problem

Sometimes, using PnP PowerShell just isn’t an option. Whether it’s because of restrictions in your environment or you simply need a quick peek at your SharePoint data, you need another way. I often find myself needing a fast report on lists, fields, subsites, etc.

Solution: JavaScript in the Browser Console for a Quick Report

Enter JavaScript! I know, it might sound a bit unconventional, but running JavaScript right in the browser console has come to the rescue more than once. This method gives me a quick report on the fly. No extra tools required. It’s not the most elegant approach out there, but when you need to discover your SharePoint data fast, it really gets the job done.

This is me, working on a SharePoint project
This is me, working on a SharePoint project

How to Use the Scripts

  • Navigate to your SharePoint site or OneDrive site
  • Open Browser console
  • type “allow pasting”
  • Press enter
  • Then copy any code snippet from this page
  • Paste the copied code to browser console
  • Press enter
  • Enjoy the results!

Get List of Lists and Content Types

Below is a small collection of scripts that does the heavy lifting for you. It starts at your current SharePoint site, grabs all the subsites (webs) recursively, and then pulls in every list along with its content types. Once it’s done, it shows you a neat table right in your browser. You can even downloads a CSV report. Super handy, don’t you agree? 😃

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
(async function() {
// Get current site URL from window location
const currentUrl = window.location.href;
const siteUrl = currentUrl.split('/_layouts')[0].split('/Lists')[0].split('/Forms')[0].split('/SitePages')[0];
// Create UI elements
const container = document.createElement('div');
container.style.cssText = 'position:fixed;top:20px;right:20px;width:98%;max-width:1600px;background:white;border:2px solid #0078d4;padding:20px;z-index:10000;max-height:80vh;overflow-y:auto;box-shadow:0 4px 6px rgba(0,0,0,0.1);user-select:text;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;';
const controls = document.createElement('div');
controls.style.cssText = 'margin-bottom:20px;display:flex;align-items:center;';
const downloadButton = document.createElement('button');
downloadButton.textContent = 'Download CSV';
downloadButton.style.cssText = 'padding:8px 16px;margin-right:10px;background:#107c10;color:white;border:none;cursor:pointer;display:none;';
const infoText = document.createElement('span');
infoText.style.cssText = 'margin-right:auto;font-weight:bold;color:#107c10;';
const closeButton = document.createElement('button');
closeButton.textContent = 'Close';
closeButton.style.cssText = 'padding:8px 16px;background:#d83b01;color:white;border:none;cursor:pointer;margin-left:20px;';
const tableContainer = document.createElement('div');
controls.appendChild(downloadButton);
controls.appendChild(infoText);
controls.appendChild(closeButton);
container.appendChild(controls);
container.appendChild(tableContainer);
document.body.appendChild(container);
let allContentTypesData = [];
let rootWebUrl = '';
// Recursively fetch all subsites (webs)
async function fetchWebAndSubsites(webUrl) {
const allWebs = [];
try {
// Get current web info
const webResponse = await fetch(`${webUrl}/_api/web?$select=Title,Url,ServerRelativeUrl`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (!webResponse.ok) {
throw new Error('Failed to fetch web info for: ' + webUrl);
}
const webData = await webResponse.json();
const currentWeb = webData.d;
allWebs.push({
Title: currentWeb.Title,
Url: currentWeb.Url,
ServerRelativeUrl: currentWeb.ServerRelativeUrl
});
// Get child webs
const subWebsResponse = await fetch(`${webUrl}/_api/web/webs?$select=Title,Url,ServerRelativeUrl`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (subWebsResponse.ok) {
const subWebsData = await subWebsResponse.json();
const subWebs = subWebsData.d.results;
// Recursively get all child webs
for (const subWeb of subWebs) {
const childWebs = await fetchWebAndSubsites(subWeb.Url);
allWebs.push(...childWebs);
}
}
} catch (error) {
console.error(`Error fetching web ${webUrl}:`, error);
}
return allWebs;
}
// Fetch lists for a given web
async function fetchWebLists(webUrl) {
try {
const response = await fetch(`${webUrl}/_api/web/lists?$select=Title,Id,BaseTemplate,DefaultViewUrl&$filter=Hidden eq false`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (!response.ok) {
throw new Error('Failed to fetch lists for: ' + webUrl);
}
const data = await response.json();
return data.d.results;
} catch (error) {
console.error(`Error fetching lists for ${webUrl}:`, error);
return [];
}
}
// Load all data
const loadData = async () => {
try {
tableContainer.innerHTML = '<div style="color:#0078d4;font-style:italic;">Loading all sites and their lists with content types...</div>';
allContentTypesData = [];
// First get the web context to ensure we have the right URL
const contextResponse = await fetch(`${siteUrl}/_api/web`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (!contextResponse.ok) {
throw new Error('Failed to get site context');
}
const contextData = await contextResponse.json();
rootWebUrl = contextData.d.Url;
// Get all webs recursively
infoText.textContent = 'Fetching all sites...';
const allWebs = await fetchWebAndSubsites(rootWebUrl);
// For each web, get its lists and content types
let totalSites = allWebs.length;
let processedSites = 0;
for (const web of allWebs) {
processedSites++;
infoText.textContent = `Processing site ${processedSites} of ${totalSites}: ${web.Title}`;
const lists = await fetchWebLists(web.Url);
// For each list, get content types
for (const list of lists) {
try {
const ctResponse = await fetch(`${web.Url}/_api/web/lists/getbytitle('${encodeURIComponent(list.Title)}')/contenttypes?$select=Name,Id,Description,Group,Hidden,ReadOnly`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (ctResponse.ok) {
const ctData = await ctResponse.json();
const contentTypes = ctData.d.results;
// Create a row for each content type
contentTypes.forEach(ct => {
allContentTypesData.push({
SiteTitle: web.Title,
SiteUrl: web.Url,
SiteRelativeUrl: web.ServerRelativeUrl,
ListTitle: list.Title,
ListUrl: list.DefaultViewUrl,
ContentTypeName: ct.Name,
ContentTypeId: ct.Id.StringValue,
Description: ct.Description || '',
Group: ct.Group || '',
Hidden: ct.Hidden ? 'Yes' : 'No',
ReadOnly: ct.ReadOnly ? 'Yes' : 'No'
});
});
}
} catch (error) {
console.error(`Error loading content types for ${web.Title}/${list.Title}:`, error);
}
}
}
// Display info
const totalCTs = allContentTypesData.length;
const uniqueLists = new Set(allContentTypesData.map(item => `${item.SiteUrl}|${item.ListTitle}`)).size;
infoText.textContent = `Total sites: ${totalSites}, Total lists: ${uniqueLists}, Total content types: ${totalCTs}`;
// Create table
let tableHTML = `
<table style="width:100%;border-collapse:collapse;margin-top:10px;user-select:text;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;">
<thead>
<tr style="background:#0078d4;color:white;">
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Site Title</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Site URL</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">List Title</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">List URL</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Content Type Name</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;width:180px;max-width:180px;user-select:text;">Content Type ID</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;width:150px;user-select:text;">Description</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Group</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Hidden</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Read Only</th>
</tr>
</thead>
<tbody>
`;
let currentSite = '';
let currentList = '';
allContentTypesData.forEach((row, index) => {
const bgColor = index % 2 === 0 ? '#f2f2f2' : 'white';
const siteChanged = row.SiteUrl !== currentSite;
const listChanged = siteChanged || `${row.SiteUrl}|${row.ListTitle}` !== currentList;
const siteStyle = siteChanged ? 'font-weight:bold;border-top:3px solid #0078d4;' : '';
const listStyle = listChanged ? 'font-weight:bold;border-top:2px solid #0078d4;' : '';
currentSite = row.SiteUrl;
currentList = `${row.SiteUrl}|${row.ListTitle}`;
// Create clickable URLs
const listUrlHtml = row.ListUrl ?
`<a href="${row.SiteUrl}${row.ListUrl}" target="_blank" style="color:#0078d4;text-decoration:none;">Open</a>` :
'';
tableHTML += `
<tr style="background:${bgColor};">
<td style="border:1px solid #ddd;padding:8px;${siteStyle}user-select:text;cursor:text;">${row.SiteTitle}</td>
<td style="border:1px solid #ddd;padding:8px;${siteStyle}user-select:text;cursor:text;font-size:12px;">${row.SiteRelativeUrl}</td>
<td style="border:1px solid #ddd;padding:8px;${listStyle}user-select:text;cursor:text;">${row.ListTitle}</td>
<td style="border:1px solid #ddd;padding:8px;text-align:center;user-select:text;">${listUrlHtml}</td>
<td style="border:1px solid #ddd;padding:8px;user-select:text;cursor:text;">${row.ContentTypeName}</td>
<td style="border:1px solid #ddd;padding:8px;font-size:11px;word-break:break-all;width:180px;max-width:180px;user-select:text;cursor:text;">${row.ContentTypeId}</td>
<td style="border:1px solid #ddd;padding:8px;user-select:text;cursor:text;font-size:12px;width:150px;overflow:hidden;text-overflow:ellipsis;" title="${row.Description.replace(/"/g, '&quot;')}">${row.Description}</td>
<td style="border:1px solid #ddd;padding:8px;user-select:text;cursor:text;">${row.Group}</td>
<td style="border:1px solid #ddd;padding:8px;text-align:center;user-select:text;cursor:text;">${row.Hidden}</td>
<td style="border:1px solid #ddd;padding:8px;text-align:center;user-select:text;cursor:text;">${row.ReadOnly}</td>
</tr>
`;
});
tableHTML += '</tbody></table>';
tableContainer.innerHTML = tableHTML;
// Show download button
downloadButton.style.display = 'inline-block';
// Also log to console
console.table(allContentTypesData);
} catch (error) {
console.error('Error loading data:', error);
tableContainer.innerHTML = `<div style="color:red;">Error: ${error.message}</div>`;
}
};
// Download CSV
downloadButton.onclick = () => {
if (!allContentTypesData.length) return;
// Create CSV content
let csv = 'Site Title,Site URL,List Title,List URL,Content Type Name,Content Type ID,Description,Group,Hidden,Read Only\n';
allContentTypesData.forEach(row => {
const fullListUrl = row.ListUrl ? `${row.SiteUrl}${row.ListUrl}` : '';
csv += `"${(row.SiteTitle || '').replace(/"/g, '""')}","${(row.SiteUrl || '').replace(/"/g, '""')}","${(row.ListTitle || '').replace(/"/g, '""')}","${fullListUrl.replace(/"/g, '""')}","${(row.ContentTypeName || '').replace(/"/g, '""')}","${(row.ContentTypeId || '').replace(/"/g, '""')}","${(row.Description || '').replace(/"/g, '""')}","${(row.Group || '').replace(/"/g, '""')}","${row.Hidden}","${row.ReadOnly}"\n`;
});
// Download
const blob = new Blob([csv], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `all_sites_lists_content_types_${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
};
// Close button
closeButton.onclick = () => {
document.body.removeChild(container);
};
// Auto-load on start
loadData();
})();

Results:

Get list of lists and content types
Get list of lists and content types

Get the List of Lists and Libraries. Advanced Version.

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
(async function() {
// Get current site URL - will be determined from context
let siteUrl = '';
// Create floating button that persists
const floatingButton = document.createElement('button');
floatingButton.innerHTML = '📋 Lists';
floatingButton.style.cssText = 'position:fixed;bottom:20px;right:20px;padding:12px 20px;background:#0078d4;color:white;border:none;border-radius:25px;cursor:pointer;z-index:9999;box-shadow:0 4px 6px rgba(0,0,0,0.2);font-size:14px;font-weight:bold;display:none;';
floatingButton.onmouseover = function() { this.style.background = '#106ebe'; };
floatingButton.onmouseout = function() { this.style.background = '#0078d4'; };
document.body.appendChild(floatingButton);
// Create UI elements
const container = document.createElement('div');
container.style.cssText = 'position:fixed;top:20px;right:20px;width:95%;max-width:1500px;background:white;border:2px solid #0078d4;padding:20px;z-index:10000;max-height:80vh;overflow-y:auto;box-shadow:0 4px 6px rgba(0,0,0,0.1);user-select:text;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;';
const controls = document.createElement('div');
controls.style.cssText = 'margin-bottom:20px;display:flex;align-items:center;';
// Filter input
const filterInput = document.createElement('input');
filterInput.type = 'text';
filterInput.placeholder = 'Filter lists by title...';
filterInput.style.cssText = 'padding:8px;margin-right:10px;border:1px solid #ccc;border-radius:4px;width:200px;';
// Toggle hidden lists button
const toggleHiddenButton = document.createElement('button');
toggleHiddenButton.textContent = '👁️ Show System Lists';
toggleHiddenButton.style.cssText = 'padding:8px 16px;margin-right:10px;background:#6c757d;color:white;border:none;cursor:pointer;';
const downloadButton = document.createElement('button');
downloadButton.textContent = 'Download CSV';
downloadButton.style.cssText = 'padding:8px 16px;margin-right:10px;background:#107c10;color:white;border:none;cursor:pointer;display:none;';
const infoText = document.createElement('span');
infoText.style.cssText = 'margin-right:auto;font-weight:bold;color:#107c10;';
const closeButton = document.createElement('button');
closeButton.textContent = 'Close';
closeButton.style.cssText = 'padding:8px 16px;background:#d83b01;color:white;border:none;cursor:pointer;margin-left:20px;';
const contentContainer = document.createElement('div');
controls.appendChild(filterInput);
controls.appendChild(toggleHiddenButton);
controls.appendChild(downloadButton);
controls.appendChild(infoText);
controls.appendChild(closeButton);
container.appendChild(controls);
container.appendChild(contentContainer);
document.body.appendChild(container);
let allListsData = [];
let selectedListId = null;
let showHiddenLists = false;
// Create modal for list details
function createDetailsModal(list) {
// Create overlay
const overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.3);z-index:10000;';
const modal = document.createElement('div');
modal.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:90%;max-width:800px;background:white;border:2px solid #0078d4;z-index:10001;box-shadow:0 8px 16px rgba(0,0,0,0.2);max-height:80vh;overflow:hidden;';
const modalContent = `
<div style="background:#0078d4;padding:10px 20px;cursor:move;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;" id="modalHeader">
<h2 style="margin:0;color:white;">${list.Title}</h2>
</div>
<div style="padding:20px;overflow-y:auto;max-height:calc(80vh - 60px);">
<div style="display:grid;grid-template-columns:150px 1fr;gap:10px;color:#333;">
<strong style="color:#333;">List GUID:</strong><span style="user-select:text;font-family:monospace;color:#333;">${list.Id}</span>
<strong style="color:#333;">Title:</strong><span style="color:#333;">${list.Title}</span>
<strong style="color:#333;">Description:</strong><span style="color:#333;">${list.Description || 'No description'}</span>
<strong style="color:#333;">Item Count:</strong><span style="color:#333;">${list.ItemCount.toLocaleString()}</span>
<strong style="color:#333;">List URL:</strong><span style="word-break:break-all;color:#333;"><a href="${list.ListUrl}" target="_blank" style="color:#0078d4;">${list.ListUrl}</a></span>
<strong style="color:#333;">Default View URL:</strong><span style="word-break:break-all;color:#333;"><a href="${list.DefaultViewUrl}" target="_blank" style="color:#0078d4;">${list.DefaultViewUrl}</a></span>
<strong style="color:#333;">Root Folder:</strong><span style="color:#333;">${list.RootFolder}</span>
<strong style="color:#333;">Entity Type:</strong><span style="color:#333;">${list.EntityTypeName}</span>
<strong style="color:#333;">Base Template:</strong><span style="color:#333;">${list.BaseTemplate} (${list.BaseType})</span>
<strong style="color:#333;">Created:</strong><span style="color:#333;">${list.Created}</span>
<strong style="color:#333;">Last Modified:</strong><span style="color:#333;">${list.LastItemModifiedDate}</span>
<strong style="color:#333;">Last Deleted:</strong><span style="color:#333;">${list.LastItemDeletedDate}</span>
<strong style="color:#333;">Major Version Limit:</strong><span style="color:#333;">${list.MajorVersionLimit}</span>
<strong style="color:#333;">Major with Minor Limit:</strong><span style="color:#333;">${list.MajorWithMinorVersionsLimit}</span>
<strong style="color:#333;">Draft Version Visibility:</strong><span style="color:#333;">${list.DraftVersionVisibility}</span>
<strong style="color:#333;">Content Types Enabled:</strong><span style="color:#333;">${list.ContentTypesEnabled}</span>
<strong style="color:#333;">Attachments Enabled:</strong><span style="color:#333;">${list.EnableAttachments}</span>
<strong style="color:#333;">Folder Creation:</strong><span style="color:#333;">${list.EnableFolderCreation}</span>
<strong style="color:#333;">Versioning Enabled:</strong><span style="color:#333;">${list.EnableVersioning}</span>
<strong style="color:#333;">Minor Versions Enabled:</strong><span style="color:#333;">${list.EnableMinorVersions}</span>
<strong style="color:#333;">Moderation Enabled:</strong><span style="color:#333;">${list.EnableModeration}</span>
<strong style="color:#333;">Force Checkout:</strong><span style="color:#333;">${list.ForceCheckout}</span>
<strong style="color:#333;">Hidden:</strong><span style="color:#333;">${list.Hidden}</span>
<strong style="color:#333;">IRM Enabled:</strong><span style="color:#333;">${list.IrmEnabled}</span>
<strong style="color:#333;">Is Catalog:</strong><span style="color:#333;">${list.IsCatalog}</span>
<strong style="color:#333;">Is Private List:</strong><span style="color:#333;">${list.IsPrivate}</span>
<strong style="color:#333;">Is System List:</strong><span style="color:#333;">${list.IsSystemList}</span>
<strong style="color:#333;">Allow Deletion:</strong><span style="color:#333;">${list.AllowDeletion}</span>
<strong style="color:#333;">Has Unique Permissions:</strong><span style="color:#333;">${list.HasUniqueRoleAssignments}</span>
<strong style="color:#333;">Web URL:</strong><span style="word-break:break-all;color:#333;">${list.WebUrl}</span>
<strong style="color:#333;">Web Title:</strong><span style="color:#333;">${list.WebTitle}</span>
</div>
<div style="margin-top:20px;text-align:right;">
<button id="closeModal" style="padding:8px 16px;background:#0078d4;color:white;border:none;cursor:pointer;">Close</button>
<button id="openList" style="padding:8px 16px;background:#107c10;color:white;border:none;cursor:pointer;margin-left:10px;">Open List</button>
<button id="openSettings" style="padding:8px 16px;background:#8b4513;color:white;border:none;cursor:pointer;margin-left:10px;">List Settings</button>
</div>
</div>
`;
modal.innerHTML = modalContent;
document.body.appendChild(overlay);
document.body.appendChild(modal);
// Make modal draggable
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
const dragHeader = document.getElementById('modalHeader');
// Get initial position
const rect = modal.getBoundingClientRect();
currentX = rect.left + rect.width / 2;
currentY = rect.top + rect.height / 2;
function dragStart(e) {
if (e.target === dragHeader || dragHeader.contains(e.target)) {
isDragging = true;
initialX = e.clientX - currentX;
initialY = e.clientY - currentY;
modal.style.transform = 'none';
modal.style.left = (currentX - rect.width / 2) + 'px';
modal.style.top = (currentY - rect.height / 2) + 'px';
}
}
function dragEnd(e) {
isDragging = false;
}
function drag(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
modal.style.left = (currentX - rect.width / 2) + 'px';
modal.style.top = (currentY - rect.height / 2) + 'px';
}
}
dragHeader.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
// Function to close modal
const closeModal = () => {
document.removeEventListener('mousemove', drag);
document.removeEventListener('mouseup', dragEnd);
document.body.removeChild(modal);
document.body.removeChild(overlay);
document.removeEventListener('keydown', escHandler);
};
// Modal event handlers
document.getElementById('closeModal').onclick = closeModal;
document.getElementById('openList').onclick = () => {
window.open(list.DefaultViewUrl, '_blank');
};
document.getElementById('openSettings').onclick = () => {
window.open(list.ListSettingsUrl, '_blank');
};
// Close on overlay click (click away)
overlay.onclick = closeModal;
// Close on ESC key
const escHandler = (e) => {
if (e.key === 'Escape') {
closeModal();
}
};
document.addEventListener('keydown', escHandler);
}
// Fetch all lists from all webs in site collection
async function fetchAllLists(webUrl, webTitle = '') {
const lists = [];
try {
// Get lists from current web with comprehensive data
const listsResponse = await fetch(`${webUrl}/_api/web/lists?$select=Id,Title,Description,ItemCount,RootFolder/ServerRelativeUrl,EntityTypeName,BaseTemplate,BaseType,Created,LastItemModifiedDate,LastItemDeletedDate,DefaultViewUrl,MajorVersionLimit,MajorWithMinorVersionsLimit,DraftVersionVisibility,ContentTypesEnabled,EnableAttachments,EnableFolderCreation,EnableVersioning,EnableMinorVersions,EnableModeration,ForceCheckout,Hidden,IrmEnabled,IsCatalog,IsPrivate,IsSystemList,AllowDeletion,HasUniqueRoleAssignments,ParentWebUrl&$expand=RootFolder`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (listsResponse.ok) {
const listsData = await listsResponse.json();
const webLists = listsData.d.results;
for (const list of webLists) {
const listInfo = {
Id: list.Id,
Title: list.Title,
Description: list.Description || '',
ItemCount: list.ItemCount,
ListUrl: `${webUrl}/Lists/${encodeURIComponent(list.Title)}`,
DefaultViewUrl: list.DefaultViewUrl,
ListSettingsUrl: `${webUrl}/_layouts/15/listedit.aspx?List={${list.Id}}`,
RootFolder: list.RootFolder.ServerRelativeUrl,
EntityTypeName: list.EntityTypeName,
BaseTemplate: list.BaseTemplate,
BaseType: list.BaseType,
Created: new Date(list.Created).toLocaleString(),
LastItemModifiedDate: list.LastItemModifiedDate ? new Date(list.LastItemModifiedDate).toLocaleString() : 'Never',
LastItemDeletedDate: list.LastItemDeletedDate ? new Date(list.LastItemDeletedDate).toLocaleString() : 'Never',
MajorVersionLimit: list.MajorVersionLimit || 'N/A',
MajorWithMinorVersionsLimit: list.MajorWithMinorVersionsLimit || 'N/A',
DraftVersionVisibility: list.DraftVersionVisibility === 0 ? 'Reader' : list.DraftVersionVisibility === 1 ? 'Author' : 'Approver',
ContentTypesEnabled: list.ContentTypesEnabled ? 'Yes' : 'No',
EnableAttachments: list.EnableAttachments ? 'Yes' : 'No',
EnableFolderCreation: list.EnableFolderCreation ? 'Yes' : 'No',
EnableVersioning: list.EnableVersioning ? 'Yes' : 'No',
EnableMinorVersions: list.EnableMinorVersions ? 'Yes' : 'No',
EnableModeration: list.EnableModeration ? 'Yes' : 'No',
ForceCheckout: list.ForceCheckout ? 'Yes' : 'No',
Hidden: list.Hidden ? 'Yes' : 'No',
IrmEnabled: list.IrmEnabled ? 'Yes' : 'No',
IsCatalog: list.IsCatalog ? 'Yes' : 'No',
IsPrivate: list.IsPrivate ? 'Yes' : 'No',
IsSystemList: list.IsSystemList ? 'Yes' : 'No',
AllowDeletion: list.AllowDeletion ? 'Yes' : 'No',
HasUniqueRoleAssignments: list.HasUniqueRoleAssignments ? 'Yes' : 'No',
WebUrl: webUrl,
WebTitle: webTitle || webUrl
};
lists.push(listInfo);
}
}
// Get subsites and their lists recursively
const subWebsResponse = await fetch(`${webUrl}/_api/web/webs?$select=Title,Url`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (subWebsResponse.ok) {
const subWebsData = await subWebsResponse.json();
const subWebs = subWebsData.d.results;
for (const subWeb of subWebs) {
const subWebLists = await fetchAllLists(subWeb.Url, subWeb.Title);
lists.push(...subWebLists);
}
}
} catch (error) {
console.error(`Error fetching lists from ${webUrl}:`, error);
}
return lists;
}
// Render lists table
function renderLists(filterText = '') {
let filteredLists = allListsData;
// Filter by hidden status
if (!showHiddenLists) {
filteredLists = filteredLists.filter(list => list.Hidden !== 'Yes');
}
// Filter by search text
if (filterText) {
filteredLists = filteredLists.filter(list => list.Title.toLowerCase().includes(filterText.toLowerCase()));
}
let tableHTML = `
<table id="listsTable" style="width:100%;border-collapse:collapse;margin-top:10px;user-select:text;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;">
<thead>
<tr style="background:#0078d4;color:white;">
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;">Title</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;">List ID (GUID)</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;">Web</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;">Item Count</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;">Base Template</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;">Created</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;">Last Modified</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;">Hidden</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;">Actions</th>
</tr>
</thead>
<tbody>
`;
filteredLists.forEach((list, index) => {
const bgColor = index % 2 === 0 ? '#f2f2f2' : 'white';
const rowId = `list_row_${list.Id.replace(/[^a-zA-Z0-9]/g, '_')}`;
tableHTML += `
<tr data-list-id="${list.Id}" data-bg-color="${bgColor}" style="background:${bgColor};cursor:pointer;">
<td class="list-title-cell" style="border:1px solid #ddd;padding:8px;font-weight:bold;color:#0078d4;">${list.Title}</td>
<td style="border:1px solid #ddd;padding:8px;font-family:monospace;font-size:11px;color:#333;user-select:text;">${list.Id}</td>
<td style="border:1px solid #ddd;padding:8px;color:#333;">${list.WebTitle}</td>
<td style="border:1px solid #ddd;padding:8px;text-align:center;color:#333;">${list.ItemCount.toLocaleString()}</td>
<td style="border:1px solid #ddd;padding:8px;text-align:center;color:#333;">${list.BaseTemplate}</td>
<td style="border:1px solid #ddd;padding:8px;font-size:11px;color:#333;">${list.Created}</td>
<td style="border:1px solid #ddd;padding:8px;font-size:11px;color:#333;">${list.LastItemModifiedDate}</td>
<td style="border:1px solid #ddd;padding:8px;text-align:center;color:#333;">${list.Hidden}</td>
<td style="border:1px solid #ddd;padding:8px;text-align:center;">
<button class="btn-open" data-url="${list.DefaultViewUrl}" style="padding:4px 8px;background:#107c10;color:white;border:none;cursor:pointer;margin-right:5px;font-size:11px;">Open</button>
<button class="btn-settings" data-url="${list.ListSettingsUrl}" style="padding:4px 8px;background:#8b4513;color:white;border:none;cursor:pointer;font-size:11px;">Settings</button>
</td>
</tr>
`;
});
tableHTML += '</tbody></table>';
contentContainer.innerHTML = tableHTML;
// Add event listeners using delegation
const table = document.getElementById('listsTable');
if (table) {
// Handle row hover
table.addEventListener('mouseover', function(e) {
const row = e.target.closest('tr');
if (row && row.dataset.listId) {
row.style.background = '#e3f2fd';
}
});
table.addEventListener('mouseout', function(e) {
const row = e.target.closest('tr');
if (row && row.dataset.listId) {
row.style.background = row.dataset.bgColor;
}
});
// Handle clicks
table.addEventListener('click', function(e) {
// Handle title cell click
if (e.target.classList.contains('list-title-cell')) {
const row = e.target.closest('tr');
if (row && row.dataset.listId) {
const list = allListsData.find(l => l.Id === row.dataset.listId);
if (list) {
createDetailsModal(list);
}
}
}
// Handle Open button
if (e.target.classList.contains('btn-open')) {
e.stopPropagation();
const url = e.target.dataset.url;
if (url) {
window.open(url, '_blank');
}
}
// Handle Settings button
if (e.target.classList.contains('btn-settings')) {
e.stopPropagation();
const url = e.target.dataset.url;
if (url) {
window.open(url, '_blank');
}
}
});
}
// Update info text with better details
const hiddenCount = allListsData.filter(list => list.Hidden === 'Yes').length;
const visibleCount = allListsData.length - hiddenCount;
if (!showHiddenLists) {
infoText.textContent = `Showing ${filteredLists.length} of ${visibleCount} visible lists (${hiddenCount} hidden lists excluded)`;
} else {
infoText.textContent = `Showing ${filteredLists.length} of ${allListsData.length} lists (${visibleCount} visible, ${hiddenCount} hidden)`;
}
}
// Filter input handler
filterInput.oninput = (e) => {
renderLists(e.target.value);
};
// Toggle hidden lists handler
toggleHiddenButton.onclick = () => {
showHiddenLists = !showHiddenLists;
toggleHiddenButton.textContent = showHiddenLists ? '🚫 Hide System Lists' : '👁️ Show System Lists';
toggleHiddenButton.style.background = showHiddenLists ? '#8b4513' : '#6c757d';
renderLists(filterInput.value);
};
// Load all data
const loadData = async () => {
try {
contentContainer.innerHTML = '<div style="color:#0078d4;font-style:italic;">Detecting site collection context...</div>';
allListsData = [];
// Use _spPageContextInfo if available (most reliable)
let rootWebUrl = '';
let rootWebTitle = '';
if (typeof _spPageContextInfo !== 'undefined' && _spPageContextInfo.siteAbsoluteUrl) {
rootWebUrl = _spPageContextInfo.siteAbsoluteUrl;
console.log('Using _spPageContextInfo for site URL:', rootWebUrl);
} else {
// Fallback: get context from current web
const currentUrl = window.location.href.split('/_layouts')[0].split('?')[0];
const contextResponse = await fetch(`${currentUrl}/_api/web?$select=Url,Title`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (!contextResponse.ok) {
throw new Error('Failed to get site context');
}
const contextData = await contextResponse.json();
rootWebUrl = contextData.d.Url;
rootWebTitle = contextData.d.Title;
console.log('Using API context for site URL:', rootWebUrl);
}
// Get the actual site collection root
const siteResponse = await fetch(`${rootWebUrl}/_api/site/rootweb?$select=Url,Title`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (siteResponse.ok) {
const siteData = await siteResponse.json();
rootWebUrl = siteData.d.Url;
rootWebTitle = siteData.d.Title;
console.log('Using site collection root URL:', rootWebUrl);
}
// Get all lists recursively
infoText.textContent = 'Fetching all lists...';
allListsData = await fetchAllLists(rootWebUrl, rootWebTitle);
// Sort by web and then by title
allListsData.sort((a, b) => {
if (a.WebTitle !== b.WebTitle) {
return a.WebTitle.localeCompare(b.WebTitle);
}
return a.Title.localeCompare(b.Title);
});
// Display info
const totalLists = allListsData.length;
const totalItems = allListsData.reduce((sum, list) => sum + list.ItemCount, 0);
const hiddenLists = allListsData.filter(list => list.Hidden === 'Yes').length;
const visibleLists = totalLists - hiddenLists;
infoText.textContent = `Total: ${totalLists} lists (${visibleLists} visible, ${hiddenLists} hidden), ${totalItems.toLocaleString()} items`;
// Render table
renderLists();
// Show download button
downloadButton.style.display = 'inline-block';
// Also log summary to console
console.log('All Lists Summary:', {
totalLists,
visibleLists,
hiddenLists,
totalItems,
lists: allListsData
});
} catch (error) {
console.error('Error loading data:', error);
contentContainer.innerHTML = `<div style="color:red;">Error: ${error.message}</div>`;
}
};
// Download CSV
downloadButton.onclick = () => {
if (!allListsData.length) return;
// Create CSV content
let csv = 'List ID,Title,Description,Web Title,Web URL,Item Count,List URL,Default View URL,Root Folder,Entity Type,Base Template,Base Type,Created,Last Modified,Last Deleted,Hidden,System List,Private List,Content Types Enabled,Versioning Enabled,Minor Versions,Moderation,Attachments,Folder Creation,Force Checkout,IRM Enabled,Has Unique Permissions\n';
allListsData.forEach(list => {
// Helper function to safely convert to string and escape quotes
const escapeCSV = (value) => {
const str = (value || '').toString();
return str.replace(/"/g, '""');
};
csv += `"${escapeCSV(list.Id)}","${escapeCSV(list.Title)}","${escapeCSV(list.Description)}","${escapeCSV(list.WebTitle)}","${escapeCSV(list.WebUrl)}","${escapeCSV(list.ItemCount)}","${escapeCSV(list.ListUrl)}","${escapeCSV(list.DefaultViewUrl)}","${escapeCSV(list.RootFolder)}","${escapeCSV(list.EntityTypeName)}","${escapeCSV(list.BaseTemplate)}","${escapeCSV(list.BaseType)}","${escapeCSV(list.Created)}","${escapeCSV(list.LastItemModifiedDate)}","${escapeCSV(list.LastItemDeletedDate)}","${escapeCSV(list.Hidden)}","${escapeCSV(list.IsSystemList)}","${escapeCSV(list.IsPrivate)}","${escapeCSV(list.ContentTypesEnabled)}","${escapeCSV(list.EnableVersioning)}","${escapeCSV(list.EnableMinorVersions)}","${escapeCSV(list.EnableModeration)}","${escapeCSV(list.EnableAttachments)}","${escapeCSV(list.EnableFolderCreation)}","${escapeCSV(list.ForceCheckout)}","${escapeCSV(list.IrmEnabled)}","${escapeCSV(list.HasUniqueRoleAssignments)}"\n`;
});
// Add UTF-8 BOM to handle special characters correctly
const BOM = '\uFEFF';
const csvWithBOM = BOM + csv;
// Download with UTF-8 encoding
const blob = new Blob([csvWithBOM], { type: 'text/csv;charset=utf-8' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `all_lists_site_collection_${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
};
// Close button
closeButton.onclick = () => {
container.style.display = 'none';
floatingButton.style.display = 'block';
};
// Floating button click - reopen main dialog
floatingButton.onclick = () => {
container.style.display = 'block';
floatingButton.style.display = 'none';
};
// Auto-load on start
loadData();
})();

Results:

List of lists and libraries
List of lists and libraries

Get Columns for Lists and Libraries

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
(async function() {
// Excluded fields list
const excludedInternalNames = [
"FileLeafRef", "ParentLeafName", "ParentVersionString", "_UIVersionString",
"Edit", "AppEditor", "AppAuthor", "FolderChildCount", "ItemChildCount",
"FileSizeDisplay", "DocIcon", "LinkFilename", "LinkFilenameNoMenu",
"_CheckinComment", "CheckoutUser", "_CopySource", "Editor", "Modified",
"Author", "Created", "ContentType", "ID", "_ColorTag", "_ComplianceFlags",
"_ComplianceTag", "_ComplianceTagWrittenTime", "_ComplianceTagUserId", "_IsRecord",
"LinkTitleNoMenu", "LinkTitle", "ComplianceAssetId"
];
// Get current site URL from window location
const currentUrl = window.location.href;
const siteUrl = currentUrl.split('/_layouts')[0].split('/Lists')[0].split('/Forms')[0].split('/SitePages')[0];
// Create UI elements
const container = document.createElement('div');
container.style.cssText = 'position:fixed;top:20px;right:20px;width:90%;max-width:1100px;background:white;border:2px solid #0078d4;padding:20px;z-index:10000;max-height:80vh;overflow-y:auto;box-shadow:0 4px 6px rgba(0,0,0,0.1);';
const controls = document.createElement('div');
controls.style.cssText = 'margin-bottom:20px;';
const dropdown = document.createElement('select');
dropdown.style.cssText = 'padding:8px;margin-right:10px;min-width:200px;';
const viewButton = document.createElement('button');
viewButton.textContent = 'View Fields';
viewButton.style.cssText = 'padding:8px 16px;margin-right:10px;background:#0078d4;color:white;border:none;cursor:pointer;';
const downloadButton = document.createElement('button');
downloadButton.textContent = 'Download CSV';
downloadButton.style.cssText = 'padding:8px 16px;margin-right:10px;background:#107c10;color:white;border:none;cursor:pointer;display:none;';
const closeButton = document.createElement('button');
closeButton.textContent = 'Close';
closeButton.style.cssText = 'padding:8px 16px;background:#d83b01;color:white;border:none;cursor:pointer;';
const fieldCount = document.createElement('span');
fieldCount.style.cssText = 'margin-left:10px;font-weight:bold;color:#107c10;';
const tableContainer = document.createElement('div');
controls.appendChild(dropdown);
controls.appendChild(viewButton);
controls.appendChild(downloadButton);
controls.appendChild(fieldCount);
controls.appendChild(closeButton);
container.appendChild(controls);
container.appendChild(tableContainer);
document.body.appendChild(container);
// First get the web context to ensure we have the right URL
try {
const contextResponse = await fetch(`${siteUrl}/_api/web`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (!contextResponse.ok) {
throw new Error('Failed to get site context');
}
const contextData = await contextResponse.json();
const webUrl = contextData.d.Url;
// Now load lists using the confirmed web URL
const listsResponse = await fetch(`${webUrl}/_api/web/lists?$select=Title,Id,BaseTemplate&$filter=Hidden eq false`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
const listsData = await listsResponse.json();
const lists = listsData.d.results;
// Populate dropdown
dropdown.innerHTML = '<option value="">Select a list...</option>';
lists.forEach(list => {
const option = document.createElement('option');
option.value = list.Title;
option.textContent = list.Title;
dropdown.appendChild(option);
});
let currentFields = [];
// View fields button click
viewButton.onclick = async () => {
const selectedList = dropdown.value;
if (!selectedList) {
alert('Please select a list');
return;
}
try {
tableContainer.innerHTML = '<div style="color:#0078d4;font-style:italic;">Loading fields...</div>';
const fieldsResponse = await fetch(`${webUrl}/_api/web/lists/getbytitle('${encodeURIComponent(selectedList)}')/fields`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
const fieldsData = await fieldsResponse.json();
const allFields = fieldsData.d.results;
// Filter fields
currentFields = allFields.filter(field =>
!field.Hidden &&
!excludedInternalNames.includes(field.InternalName)
);
// Display count
fieldCount.textContent = `Total fields: ${currentFields.length}`;
// Create table
let tableHTML = `
<table style="width:100%;border-collapse:collapse;margin-top:10px;">
<thead>
<tr style="background:#0078d4;color:white;">
<th style="border:1px solid #ddd;padding:8px;">Title</th>
<th style="border:1px solid #ddd;padding:8px;">Internal Name</th>
<th style="border:1px solid #ddd;padding:8px;">Type</th>
<th style="border:1px solid #ddd;padding:8px;">Description</th>
<th style="border:1px solid #ddd;padding:8px;">Group</th>
</tr>
</thead>
<tbody>
`;
currentFields.forEach((field, index) => {
const bgColor = index % 2 === 0 ? '#f2f2f2' : 'white';
tableHTML += `
<tr style="background:${bgColor};">
<td style="border:1px solid #ddd;padding:8px;">${field.Title || ''}</td>
<td style="border:1px solid #ddd;padding:8px;">${field.InternalName || ''}</td>
<td style="border:1px solid #ddd;padding:8px;">${field.TypeDisplayName || ''}</td>
<td style="border:1px solid #ddd;padding:8px;">${field.Description || ''}</td>
<td style="border:1px solid #ddd;padding:8px;">${field.Group || ''}</td>
</tr>
`;
});
tableHTML += '</tbody></table>';
tableContainer.innerHTML = tableHTML;
// Show download button
downloadButton.style.display = 'inline-block';
// Also log to console
console.table(currentFields.map(f => ({
Title: f.Title,
InternalName: f.InternalName,
Type: f.TypeDisplayName,
Description: f.Description,
Group: f.Group
})));
} catch (error) {
console.error('Error loading fields:', error);
tableContainer.innerHTML = `<div style="color:red;">Error: ${error.message}</div>`;
}
};
// Download CSV
downloadButton.onclick = () => {
if (!currentFields.length) return;
const selectedList = dropdown.value;
// Create CSV content
let csv = 'Title,Internal Name,Type,Description,Group\n';
currentFields.forEach(field => {
csv += `"${(field.Title || '').replace(/"/g, '""')}","${(field.InternalName || '').replace(/"/g, '""')}","${(field.TypeDisplayName || '').replace(/"/g, '""')}","${(field.Description || '').replace(/"/g, '""')}","${(field.Group || '').replace(/"/g, '""')}"\n`;
});
// Download
const blob = new Blob([csv], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${selectedList}_fields_${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
};
} catch (error) {
console.error('Error loading lists:', error);
alert('Error loading lists: ' + error.message);
}
// Close button
closeButton.onclick = () => {
document.body.removeChild(container);
};
})();

Results:

Get all fields for the selected list
Get all fields for the selected list

Get All Webs in a Site Collection

  • Open Browser Console
  • Paste the following script and run it:
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
(async function() {
// Get current site URL from window location
const currentUrl = window.location.href;
let siteUrl = currentUrl;
// Remove common SharePoint paths to get the site URL
const pathsToRemove = [
'/_layouts',
'/Lists/',
'/Forms/',
'/SitePages/',
'/SiteAssets/',
'/Shared%20Documents/',
'/Documents/',
'/_api/',
'/Pages/'
];
for (const path of pathsToRemove) {
if (siteUrl.includes(path)) {
siteUrl = siteUrl.split(path)[0];
break;
}
}
// Handle managed paths and site collections properly
if (siteUrl.includes('/')) {
const parts = siteUrl.split('/');
// For URLs like https://collaborate.abcp.ab.bluecross.ca/sites/sitename/...
if (parts.length > 4 && parts[3] === 'sites') {
// Keep protocol, domain, managed path, and site name: https://domain/sites/sitename
siteUrl = parts.slice(0, 5).join('/');
} else if (parts.length > 3) {
// For root site collections: https://domain
siteUrl = parts.slice(0, 3).join('/');
}
}
// Create UI elements
const container = document.createElement('div');
container.style.cssText = 'position:fixed;top:20px;right:20px;width:95%;max-width:1500px;background:white;border:2px solid #0078d4;padding:20px;z-index:10000;max-height:80vh;overflow-y:auto;box-shadow:0 4px 6px rgba(0,0,0,0.1);user-select:text;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;';
const controls = document.createElement('div');
controls.style.cssText = 'margin-bottom:20px;display:flex;align-items:center;';
// Tab buttons
const tableTabButton = document.createElement('button');
tableTabButton.textContent = 'Table View';
tableTabButton.style.cssText = 'padding:8px 16px;margin-right:5px;background:#0078d4;color:white;border:none;cursor:pointer;border-radius:4px 4px 0 0;';
const treeTabButton = document.createElement('button');
treeTabButton.textContent = 'Tree View';
treeTabButton.style.cssText = 'padding:8px 16px;margin-right:15px;background:#6c757d;color:white;border:none;cursor:pointer;border-radius:4px 4px 0 0;';
const downloadButton = document.createElement('button');
downloadButton.textContent = 'Download CSV';
downloadButton.style.cssText = 'padding:8px 16px;margin-right:10px;background:#107c10;color:white;border:none;cursor:pointer;display:none;';
const infoText = document.createElement('span');
infoText.style.cssText = 'margin-right:auto;font-weight:bold;color:#107c10;';
const closeButton = document.createElement('button');
closeButton.textContent = 'Close';
closeButton.style.cssText = 'padding:8px 16px;background:#d83b01;color:white;border:none;cursor:pointer;margin-left:20px;';
const contentContainer = document.createElement('div');
controls.appendChild(tableTabButton);
controls.appendChild(treeTabButton);
controls.appendChild(downloadButton);
controls.appendChild(infoText);
controls.appendChild(closeButton);
container.appendChild(controls);
container.appendChild(contentContainer);
document.body.appendChild(container);
let allWebsData = [];
let hierarchicalData = [];
let rootWebUrl = '';
let currentView = 'table';
// Recursively fetch all subsites (webs) with comprehensive data
async function fetchWebAndSubsites(webUrl, level = 0) {
const allWebs = [];
try {
// Get current web info with all needed properties
const webResponse = await fetch(`${webUrl}/_api/web?$select=Id,Title,Url,ServerRelativeUrl,ParentWeb/ServerRelativeUrl,Created,WebTemplate,Language,HasUniqueRoleAssignments,Description,Configuration,LastItemModifiedDate,LastItemUserModifiedDate,MasterUrl,IsMultilingual,RequestAccessEmail&$expand=ParentWeb`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (!webResponse.ok) {
throw new Error('Failed to fetch web info for: ' + webUrl);
}
const webData = await webResponse.json();
const currentWeb = webData.d;
// Get list count for this web
const listsResponse = await fetch(`${webUrl}/_api/web/lists?$select=Id,ItemCount&$filter=Hidden eq false`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
let totalLists = 0;
let totalItems = 0;
if (listsResponse.ok) {
const listsData = await listsResponse.json();
const lists = listsData.d.results;
totalLists = lists.length;
totalItems = lists.reduce((sum, list) => sum + (list.ItemCount || 0), 0);
}
const webInfo = {
Id: currentWeb.Id,
Title: currentWeb.Title,
Url: currentWeb.Url,
ServerRelativeUrl: currentWeb.ServerRelativeUrl,
ParentURL: currentWeb.ParentWeb ? currentWeb.ParentWeb.ServerRelativeUrl : '',
Created: new Date(currentWeb.Created).toLocaleDateString(),
Template: currentWeb.WebTemplate,
Language: currentWeb.Language,
HasUniquePermissions: currentWeb.HasUniqueRoleAssignments ? 'Yes' : 'No',
Description: currentWeb.Description || '',
Configuration: currentWeb.Configuration || '',
LastItemModifiedDate: currentWeb.LastItemModifiedDate ? new Date(currentWeb.LastItemModifiedDate).toLocaleDateString() : '',
LastItemUserModifiedDate: currentWeb.LastItemUserModifiedDate ? new Date(currentWeb.LastItemUserModifiedDate).toLocaleDateString() : '',
MasterUrl: currentWeb.MasterUrl || '',
IsMultilingual: currentWeb.IsMultilingual ? 'Yes' : 'No',
RequestAccessEmail: currentWeb.RequestAccessEmail || '',
TotalLists: totalLists,
TotalItems: totalItems,
Level: level,
Children: []
};
allWebs.push(webInfo);
// Get child webs
const subWebsResponse = await fetch(`${webUrl}/_api/web/webs?$select=Title,Url,ServerRelativeUrl`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (subWebsResponse.ok) {
const subWebsData = await subWebsResponse.json();
const subWebs = subWebsData.d.results;
// Recursively get all child webs
for (const subWeb of subWebs) {
const childWebs = await fetchWebAndSubsites(subWeb.Url, level + 1);
webInfo.Children.push(...childWebs);
allWebs.push(...childWebs);
}
}
} catch (error) {
console.error(`Error fetching web ${webUrl}:`, error);
}
return allWebs;
}
// Build hierarchical structure for tree view
function buildHierarchy(webs) {
const webMap = new Map();
const rootWebs = [];
// Create a map of all webs
webs.forEach(web => {
webMap.set(web.ServerRelativeUrl, {
...web,
children: [],
expanded: true,
directChildrenCount: 0,
totalChildrenCount: 0
});
});
// Build parent-child relationships
webs.forEach(web => {
if (web.ParentURL) {
const parent = webMap.get(web.ParentURL);
if (parent) {
parent.children.push(webMap.get(web.ServerRelativeUrl));
parent.directChildrenCount++;
} else {
rootWebs.push(webMap.get(web.ServerRelativeUrl));
}
} else {
rootWebs.push(webMap.get(web.ServerRelativeUrl));
}
});
// Calculate total children count recursively
function calculateTotalChildren(node) {
let total = node.children.length;
node.children.forEach(child => {
total += calculateTotalChildren(child);
});
node.totalChildrenCount = total;
return total;
}
rootWebs.forEach(root => calculateTotalChildren(root));
return rootWebs;
}
// Render tree view
function renderTreeView(webs, level = 0, isLast = [], parentPath = '') {
let html = '';
webs.forEach((web, index) => {
const isLastChild = index === webs.length - 1;
const currentIsLast = [...isLast, isLastChild];
const nodeId = `node_${web.ServerRelativeUrl.replace(/[^a-zA-Z0-9]/g, '_')}`;
// Build tree connector lines
let connector = '';
for (let i = 0; i < level; i++) {
if (i === level - 1) {
connector += isLastChild ? '└─ ' : '├─ ';
} else {
connector += isLast[i] ? '&nbsp;&nbsp;&nbsp; ' : '│&nbsp;&nbsp; ';
}
}
const hasChildren = web.children && web.children.length > 0;
// Expand/collapse icon
let expandIconHtml = '';
if (hasChildren) {
const expandIcon = web.expanded ? '[-]' : '[+]';
expandIconHtml = `<span class="expand-icon" data-node="${nodeId}" style="color:#0066cc;cursor:pointer;margin-right:4px;user-select:none;font-weight:bold;">${expandIcon}</span>`;
}
// Create clickable links
const siteContentsUrl = `${web.Url}/_layouts/15/viewlsts.aspx`;
const metricsUrl = `${web.Url}/_layouts/15/usage.aspx`;
// Children count display
let childrenInfo = '';
if (hasChildren) {
if (web.directChildrenCount === web.totalChildrenCount) {
childrenInfo = ` [${web.directChildrenCount} child sites]`;
} else {
childrenInfo = ` [${web.directChildrenCount} direct, ${web.totalChildrenCount} total child sites]`;
}
}
html += `
<div style="font-family:'Courier New',monospace;font-size:12px;line-height:1.2;margin:0;padding:2px 0;white-space:nowrap;border-bottom:1px dotted #ddd;">
<span style="color:#666;user-select:none;">${connector}</span>
${expandIconHtml}
<span style="color:#0078d4;font-weight:bold;margin-right:8px;">${web.Title}</span>
<span style="color:#222;margin-right:8px;font-size:11px;font-weight:bold;">${web.ServerRelativeUrl}</span>
<span style="color:#8b4513;margin-right:6px;font-size:10px;font-weight:bold;">[${web.Template}]</span>
<span style="color:#006400;margin-right:6px;font-size:10px;font-weight:bold;">${web.TotalLists} lists</span>
<span style="color:#8b0000;margin-right:8px;font-size:10px;font-weight:bold;">${web.TotalItems} items</span>
<span style="color:#000080;margin-right:6px;font-size:10px;font-weight:bold;">${childrenInfo}</span>
<a href="${siteContentsUrl}" target="_blank" style="color:#1565c0;text-decoration:none;margin-right:6px;font-size:10px;">Contents</a>
<a href="${metricsUrl}" target="_blank" style="color:#1565c0;text-decoration:none;font-size:10px;">Metrics</a>
</div>
`;
if (hasChildren && web.expanded) {
html += `<div id="${nodeId}_children">${renderTreeView(web.children, level + 1, currentIsLast, nodeId)}</div>`;
} else if (hasChildren) {
html += `<div id="${nodeId}_children" style="display:none;">${renderTreeView(web.children, level + 1, currentIsLast, nodeId)}</div>`;
}
});
return html;
}
// Show table view
function showTableView() {
currentView = 'table';
tableTabButton.style.background = '#0078d4';
treeTabButton.style.background = '#6c757d';
let tableHTML = `
<table style="width:100%;border-collapse:collapse;margin-top:10px;user-select:text;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;">
<thead>
<tr style="background:#0078d4;color:white;">
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Title</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Relative URL</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Created</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Template</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Language</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Unique Permissions</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Total Lists</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">Total Items</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">View Site Contents</th>
<th style="border:1px solid #ddd;padding:8px;position:sticky;top:0;background:#0078d4;user-select:text;">View Metrics</th>
</tr>
</thead>
<tbody>
`;
allWebsData.forEach((web, index) => {
const bgColor = index % 2 === 0 ? '#f2f2f2' : 'white';
// Create clickable links
const siteContentsUrl = `${web.Url}/_layouts/15/viewlsts.aspx`;
const metricsUrl = `${web.Url}/_layouts/15/usage.aspx`;
const siteContentsLink = `<a href="${siteContentsUrl}" target="_blank" style="color:#0078d4;text-decoration:none;">View Contents</a>`;
const metricsLink = `<a href="${metricsUrl}" target="_blank" style="color:#0078d4;text-decoration:none;">View Metrics</a>`;
tableHTML += `
<tr style="background:${bgColor};">
<td style="border:1px solid #ddd;padding:8px;user-select:text;cursor:text;">${web.Title}</td>
<td style="border:1px solid #ddd;padding:8px;user-select:text;cursor:text;font-size:12px;">${web.ServerRelativeUrl}</td>
<td style="border:1px solid #ddd;padding:8px;user-select:text;cursor:text;">${web.Created}</td>
<td style="border:1px solid #ddd;padding:8px;user-select:text;cursor:text;">${web.Template}</td>
<td style="border:1px solid #ddd;padding:8px;user-select:text;cursor:text;text-align:center;">${web.Language}</td>
<td style="border:1px solid #ddd;padding:8px;user-select:text;cursor:text;text-align:center;">${web.HasUniquePermissions}</td>
<td style="border:1px solid #ddd;padding:8px;user-select:text;cursor:text;text-align:center;">${web.TotalLists}</td>
<td style="border:1px solid #ddd;padding:8px;user-select:text;cursor:text;text-align:center;">${web.TotalItems}</td>
<td style="border:1px solid #ddd;padding:8px;text-align:center;">${siteContentsLink}</td>
<td style="border:1px solid #ddd;padding:8px;text-align:center;">${metricsLink}</td>
</tr>
`;
});
tableHTML += '</tbody></table>';
contentContainer.innerHTML = tableHTML;
}
// Toggle node expansion
function toggleNode(nodeId) {
const childrenDiv = document.getElementById(`${nodeId}_children`);
const expandIcon = document.querySelector(`[data-node="${nodeId}"]`);
if (childrenDiv && expandIcon) {
const isVisible = childrenDiv.style.display !== 'none';
childrenDiv.style.display = isVisible ? 'none' : 'block';
expandIcon.textContent = isVisible ? '[+]' : '[-]';
}
}
// Show tree view
function showTreeView() {
currentView = 'tree';
treeTabButton.style.background = '#0078d4';
tableTabButton.style.background = '#6c757d';
const treeHTML = `
<div style="margin-top:10px;border:1px solid #ddd;background:#f8f8f8;padding:15px;max-height:60vh;overflow:auto;">
<div style="margin-bottom:10px;color:#0078d4;font-weight:bold;font-size:14px;">Site Collection Hierarchy</div>
<div style="font-size:11px;color:#666;margin-bottom:10px;">Click [+]/[-] to expand/collapse branches</div>
<div style="background:white;border:1px inset #ccc;padding:8px;">
${renderTreeView(hierarchicalData)}
</div>
</div>
`;
contentContainer.innerHTML = treeHTML;
// Add click handlers for expand/collapse
document.querySelectorAll('.expand-icon').forEach(icon => {
if (icon.style.cursor === 'pointer') {
icon.addEventListener('click', function(e) {
e.preventDefault();
toggleNode(this.getAttribute('data-node'));
});
}
});
}
// Load all data
const loadData = async () => {
try {
contentContainer.innerHTML = '<div style="color:#0078d4;font-style:italic;">Loading all webs in site collection...</div>';
allWebsData = [];
// First get the web context to ensure we have the right URL
const contextResponse = await fetch(`${siteUrl}/_api/web`, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
if (!contextResponse.ok) {
throw new Error('Failed to get site context');
}
const contextData = await contextResponse.json();
rootWebUrl = contextData.d.Url;
// Get all webs recursively
infoText.textContent = 'Fetching all webs...';
allWebsData = await fetchWebAndSubsites(rootWebUrl);
// Build hierarchical structure
hierarchicalData = buildHierarchy(allWebsData);
// Display info
const totalWebs = allWebsData.length;
const totalLists = allWebsData.reduce((sum, web) => sum + web.TotalLists, 0);
const totalItems = allWebsData.reduce((sum, web) => sum + web.TotalItems, 0);
infoText.textContent = `Total webs: ${totalWebs}, Total lists: ${totalLists}, Total items: ${totalItems}`;
// Show default view (table)
showTableView();
// Show download button
downloadButton.style.display = 'inline-block';
// Also log to console
console.table(allWebsData);
} catch (error) {
console.error('Error loading data:', error);
contentContainer.innerHTML = `<div style="color:red;">Error: ${error.message}</div>`;
}
};
// Tab click handlers
tableTabButton.onclick = showTableView;
treeTabButton.onclick = showTreeView;
// Download CSV
downloadButton.onclick = () => {
if (!allWebsData.length) return;
// Create CSV content
let csv = 'Id,Title,Relative URL,Parent URL,Full URL,Created,Template,Language,Has Unique Permissions,Description,Configuration,Last Item Modified,Last Item User Modified,Master URL,Is Multilingual,Request Access Email,Total Lists,Total Items,Site Contents URL,Metrics URL\n';
allWebsData.forEach(web => {
const siteContentsUrl = `${web.Url}/_layouts/15/viewlsts.aspx`;
const metricsUrl = `${web.Url}/_layouts/15/usage.aspx`;
// Helper function to safely convert to string and escape quotes
const escapeCSV = (value) => {
const str = (value || '').toString();
return str.replace(/"/g, '""');
};
csv += `"${escapeCSV(web.Id)}","${escapeCSV(web.Title)}","${escapeCSV(web.ServerRelativeUrl)}","${escapeCSV(web.ParentURL)}","${escapeCSV(web.Url)}","${escapeCSV(web.Created)}","${escapeCSV(web.Template)}","${escapeCSV(web.Language)}","${escapeCSV(web.HasUniquePermissions)}","${escapeCSV(web.Description)}","${escapeCSV(web.Configuration)}","${escapeCSV(web.LastItemModifiedDate)}","${escapeCSV(web.LastItemUserModifiedDate)}","${escapeCSV(web.MasterUrl)}","${escapeCSV(web.IsMultilingual)}","${escapeCSV(web.RequestAccessEmail)}","${escapeCSV(web.TotalLists)}","${escapeCSV(web.TotalItems)}","${escapeCSV(siteContentsUrl)}","${escapeCSV(metricsUrl)}"\n`;
});
// Download
const blob = new Blob([csv], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `all_webs_site_collection_hierarchy_${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
};
// Close button
closeButton.onclick = () => {
document.body.removeChild(container);
};
// Auto-load on start
loadData();
})();

Result:

View Subsites
View Subsites

Get All Web Permissions in Site Collection

  • Open Browser Console
  • Paste the following script and run it:
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
/**
* SharePoint Subsite Permissions Reporter
* Run directly in browser console on a SharePoint site
*/
// Get current site URL from window location
const currentUrl = window.location.href;
const siteUrl = currentUrl.split('/_layouts')[0].split('/Lists')[0].split('/Forms')[0].split('/SitePages')[0];
var subsitesEndpoint = "/_api/web/webs?$select=Title,ServerRelativeUrl,HasUniqueRoleAssignments";
var roleAssignmentsEndpoint = "/_api/web/roleassignments?$expand=Member";
var roleDefinitionsEndpoint = "/_api/web/roleassignments(principalid={0})/roledefinitionbindings";
// Global variable to store the permissions report
window.sharePointPermissionsReport = { sites: [], errors: [] };
function getFullUrl(serverRelativeUrl) {
const urlParts = siteUrl.match(/^(https?:\/\/[^\/]+)/);
if (urlParts && urlParts[1]) {
return urlParts[1] + serverRelativeUrl;
}
return (siteUrl + serverRelativeUrl).replace(/([^:]\/)\/+/g, "$1");
}
function fetchData(url) {
console.log("Fetching:", url);
return fetch(url, {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-Type': 'application/json'
},
credentials: 'same-origin'
}).then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
});
}
function getRoleAssignments(webUrl) {
return fetchData(webUrl + roleAssignmentsEndpoint)
.then(data => {
return data.d.results;
})
.catch(error => {
console.error(`Error getting role assignments for ${webUrl}:`, error);
window.sharePointPermissionsReport.errors.push({
URL: webUrl,
Error: `Error getting role assignments: ${error.message}`
});
return [];
});
}
function getRoleDefinitions(webUrl, principalId) {
const url = webUrl + roleDefinitionsEndpoint.replace("{0}", principalId);
return fetchData(url)
.then(data => {
return data.d.results;
})
.catch(error => {
console.error(`Error getting role definitions for principal ${principalId} at ${webUrl}:`, error);
window.sharePointPermissionsReport.errors.push({
URL: webUrl,
PrincipalId: principalId,
Error: `Error getting role definitions: ${error.message}`
});
return [];
});
}
function getPrincipalType(typeCode) {
switch(typeCode) {
case 0: return "None";
case 1: return "User";
case 2: return "Distribution List";
case 4: return "Security Group";
case 8: return "SharePoint Group";
case 15: return "All";
default: return "Unknown (" + typeCode + ")";
}
}
function processSitePermissions(webUrl, hasUniquePermissions) {
if (!hasUniquePermissions) {
console.log(`${webUrl} inherits permissions from parent`);
return Promise.resolve([]);
}
return getRoleAssignments(webUrl)
.then(assignments => {
if (assignments.length === 0) {
return [];
}
const permissionPromises = assignments.map(assignment => {
const principal = assignment.Member;
return getRoleDefinitions(webUrl, principal.Id)
.then(roles => {
return roles.map(role => {
return {
SiteUrl: webUrl,
PrincipalId: principal.Id,
PrincipalTitle: principal.Title,
PrincipalType: getPrincipalType(principal.PrincipalType),
PrincipalLogin: principal.LoginName || 'N/A',
RoleId: role.Id,
RoleName: role.Name,
RoleDescription: role.Description || 'N/A'
};
});
});
});
return Promise.all(permissionPromises)
.then(results => {
// Flatten the array of arrays
return results.flat();
});
});
}
function processSite(web, parentUrl = '') {
const webUrl = getFullUrl(web.ServerRelativeUrl);
console.log("Processing site:", webUrl);
return processSitePermissions(webUrl, web.HasUniqueRoleAssignments)
.then(permissions => {
const siteInfo = {
Title: web.Title,
URL: webUrl,
RelativeURL: web.ServerRelativeUrl,
ParentURL: parentUrl,
HasUniquePermissions: web.HasUniqueRoleAssignments ? 'Yes' : 'No (inherits)'
};
// Add site info to report
window.sharePointPermissionsReport.sites.push(siteInfo);
// Add permissions to report
permissions.forEach(perm => {
window.sharePointPermissionsReport.sites.push({
Title: web.Title + " > " + perm.PrincipalTitle,
URL: webUrl,
RelativeURL: web.ServerRelativeUrl,
ParentURL: parentUrl,
HasUniquePermissions: 'Yes',
PrincipalId: perm.PrincipalId,
PrincipalTitle: perm.PrincipalTitle,
PrincipalType: perm.PrincipalType,
PrincipalLogin: perm.PrincipalLogin,
RoleId: perm.RoleId,
RoleName: perm.RoleName,
RoleDescription: perm.RoleDescription
});
});
return fetchData(webUrl + subsitesEndpoint)
.then(subsitesData => {
const subsites = subsitesData.d.results;
console.log(`Found ${subsites.length} subsites for ${webUrl}`);
const subsitePromises = subsites.map(subsite =>
processSite(subsite, webUrl)
);
return Promise.all(subsitePromises);
})
.catch(error => {
console.error(`Error processing subsites for ${webUrl}:`, error);
window.sharePointPermissionsReport.errors.push({
URL: webUrl,
Error: `Error processing subsites: ${error.message}`
});
});
})
.catch(error => {
console.error(`Error processing site ${webUrl}:`, error);
window.sharePointPermissionsReport.errors.push({
URL: webUrl,
Error: `Error processing site: ${error.message}`
});
});
}
// Function to convert the report data to CSV
function convertToCSV(objArray) {
// Get all headers from the data
const headers = [];
objArray.forEach(obj => {
Object.keys(obj).forEach(key => {
if (!headers.includes(key)) {
headers.push(key);
}
});
});
// Create CSV header row
let csvStr = headers.join(',') + '\r\n';
// Add each data row
objArray.forEach(obj => {
const values = headers.map(header => {
let value = obj[header] === undefined ? '' : obj[header];
// Handle values with commas, quotes, or newlines
if (typeof value === 'string' && (value.includes(',') || value.includes('"') || value.includes('\n'))) {
value = '"' + value.replace(/"/g, '""') + '"';
}
return value;
});
csvStr += values.join(',') + '\r\n';
});
return csvStr;
}
// Function to download the CSV file
function downloadCSV(csvContent, fileName) {
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
// Create a URL for the blob
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', fileName);
link.style.visibility = 'hidden';
// Append the link to the DOM, click it, and then remove it
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Function to create and add a download button to the page
function addDownloadButton() {
const button = document.createElement('button');
button.innerText = 'Download SharePoint Permissions as CSV';
button.style.padding = '10px 15px';
button.style.margin = '20px 0';
button.style.backgroundColor = '#0078d4';
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.cursor = 'pointer';
button.addEventListener('click', () => {
const csvContent = convertToCSV(window.sharePointPermissionsReport.sites);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
downloadCSV(csvContent, `SharePoint_Permissions_Report_${timestamp}.csv`);
});
// Add button to the page
const container = document.createElement('div');
container.style.padding = '20px';
container.appendChild(button);
// If there are errors, add an errors download button too
if (window.sharePointPermissionsReport.errors.length > 0) {
const errorsButton = document.createElement('button');
errorsButton.innerText = 'Download Errors as CSV';
errorsButton.style.padding = '10px 15px';
errorsButton.style.margin = '20px 0 20px 10px';
errorsButton.style.backgroundColor = '#d83b01';
errorsButton.style.color = 'white';
errorsButton.style.border = 'none';
errorsButton.style.borderRadius = '4px';
errorsButton.style.cursor = 'pointer';
errorsButton.addEventListener('click', () => {
const csvContent = convertToCSV(window.sharePointPermissionsReport.errors);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
downloadCSV(csvContent, `SharePoint_Permissions_Errors_${timestamp}.csv`);
});
container.appendChild(errorsButton);
}
document.body.insertBefore(container, document.body.firstChild);
}
// Start the root site processing
console.log("Starting SharePoint permissions report...");
// First get the web context to ensure we have the right URL
fetchData(`${siteUrl}/_api/web`)
.then(contextData => {
const webUrl = contextData.d.Url;
console.log("Confirmed web URL:", webUrl);
// Now get the current site data using the confirmed URL
return fetchData(webUrl + "/_api/web?$select=Title,ServerRelativeUrl,HasUniqueRoleAssignments");
})
.then(rootWebData => {
return processSite(rootWebData.d);
})
.then(() => {
console.log('Permissions information collected. Outputting table...');
console.table(window.sharePointPermissionsReport.sites);
if (window.sharePointPermissionsReport.errors.length > 0) {
console.log('Errors encountered:');
console.table(window.sharePointPermissionsReport.errors);
}
// Add download button to the page
addDownloadButton();
console.log('Report is now available in the global variable "sharePointPermissionsReport"');
console.log('To copy the report to clipboard, run: copy(JSON.stringify(sharePointPermissionsReport))');
console.log('You can also use the download button at the top of the page to save the report as a CSV file');
})
.catch(error => console.error('Error in main process:', error));

But Denis, why not build a Chrome extension?

I hear you! Indeed, I could build a Chrome extension. However, here’s a kicker: on many SharePoint projects I work on, no Chrome extensions are allowed due to the client’s security configurations.

Why didn't you build a Chrome extension?!
Why didn't you build a Chrome extension?!

Conclusion

And that’s it! This approach is a quick and dirty solution that works. It might not be the most elegant or polished approach, but sometimes, you need something fast that gets the job done. With just a bit of vanilla JavaScript and the SharePoint REST API, you can pull all your subsites, lists, and content types, display them in a neat console table, and even download a CSV report—all right from your browser.


Tags

SharePointJavaScriptRest API

Share

Previous Article
Improve your Privacy at Work
Denis Molodtsov

Denis Molodtsov

Microsoft 365 Architect

Related Posts

PnP.PowerShell App Registration
PnP.PowerShell App Registration
August 05, 2025
1 min

Quick Links

AboutAll Posts

Social Media