Library & File Plan Manager
SharePoint permissions map (file-plan style, grouped by team) + an admin screen that creates document libraries correctly and registers them automatically, so nothing gets forgotten again.
App.OnStart (v3.12)— done, per Allan, 25 Sep 2026.- New, ready to paste: App.OnStart v3.13 (see the App.OnStart section below) — small addition on top of the confirmed-live v3.12, needed for scrGroupSearch v3.9's new indicator and refresh button. Paste this before pasting scrGroupSearch v3.9.
Paste the 3 updated screens— done: scrFilePlan v2.9, scrAdminCreateLibrary v1.9, scrGroupSearch v3.8 all pasted successfully.Paste scrManageLibraries— pasted, v2.1 had two real bugs (wrong flow name + a syntax error), both fixed in v2.2. A third error remains, but it's not a code bug: FP-DeleteLibraries(App) has never actually been run, so Power Apps has no sample response to learn its shape from — every field insideResultsshows as type "Error" until it has. Fix: Power Automate → FP-DeleteLibraries(App) → Test → Manually, run it once withAction="Check"(this mode never deletes anything, entirely safe) and any real library URL inLibraryUrlsJson. Then refresh the connection in the Power Apps Data panel and re-check.scrEditLibraries errored on v2.0— fixed in v2.1, ready to retry: scrEditLibraries v2.1. Two real control-property bugs, explained in the Screens section below. Delete any partially-pasted screen first, then paste fresh.- Confirmed 25 Sep 2026: the "Delete Libraries" flow's current name is
FP-DeleteLibraries(App)(Allan's own rename). It didn't exist in Power Automate at all until imported from DeleteLibrariesApp_flow_import.zip — if not done yet, import it (My flows → Import → Import Package (Legacy)) and add it as a data source before pasting scrManageLibraries. - Once scrManageLibraries v2.2 and scrEditLibraries v2.1 are both in: test the actual intended flow — tick a library's checkbox on Manage Libraries, click Edit Selected, confirm you land on populated cards (not empty ones). Report back if that specific path doesn't work.
- Optional, whenever you want that specific feature: Register Existing Library flow surgery (2d) for the new toggle on Create Library, and/or the Backfill name fix (2c) if you plan to run Backfill again.
- New idea, not built yet: Open Permissions Link (2e) — a button on scrGroupSearch to jump straight to a library's real SharePoint permissions page in a new tab. Needs a new
LibraryIdcolumn onPermissionIndexand a one-line addition to FP - Rebuild Permission Index first (both described in 2e) before the button itself can be added. - New, ready to paste: scrGroupSearch v3.9 (needs App.OnStart v3.13 above pasted first) — two additions on top of v3.8. First, a dual-signal LTD indicator: every library on this screen already has genuinely unique SharePoint permissions (that's how the Rebuild Permission Index flow decides what goes in the list), so each row now cross-checks that against
LibraryFilePlan.IsLimited— red padlock when they agree, purple warning triangle when the flag says "not Limited" but the real permissions disagree (a data-quality flag, not a bug). Second, a "Refresh Index" button using the sameRebuildPermissionIndex.Run(...)data-source reference already used by scrAdminCreateLibrary's own Rebuild Permission Index button, so it reuses whatever's already wired up rather than adding a second copy of that flow as a data source. This one also reloads the screen's data automatically afterwards, which the older button doesn't. Confirmed by a real test run to take about 1m 45s, long enough to risk Power Apps' own synchronous call timeout on some devices; if that happens the button shows a warning rather than silently failing, and the rebuild itself keeps running server-side regardless, so waiting a minute and pressing it again picks up the finished result. Open question carried over from before the flow rename: isRebuildPermissionIndex(the bare data-source identifier both buttons use) still correct now the flow itself is calledFP - Rebuild Permission Index? Data-source identifiers usually survive a flow rename unlike the quoted-string.Run()style that broke on Delete Libraries, but this hasn't been directly confirmed since the rename. Worth a quick check in Studio's Data panel before relying on either button. - Fixed on all 5 screens (26 Sep 2026): the disabled "Manage Permissions" nav button on every screen still had
OnSelect: =Navigate(scrManagePermissions)— a leftover from when all 6 screens were planned. Since it'sDisplayMode.Disabledit could never actually fire, but Studio flags the reference as unresolved (Allan spotted the red squiggle on it while pasting). Swapped to a harmless=falseon all 5. If you already pasted a screen before this fix, either re-paste it or just click into that one formula bar and change it yourself — not worth re-pasting the whole screen just for this. - Once anything above is pasted and confirmed live, tell me — I'll re-pull the exact flow names (they'll have changed since your rename) and update this box.
- SharePoint lists — create
LibraryFilePlanandPermissionIndexvia the CSV templates (fastest — creates every column for you), or manually per the schema tables. Column isPrettyName, notDisplayName— SharePoint silently drops writes to that name on this list. - Flow field fix — in Create Library (App), edit
Create_FilePlan_Row's Body so it writesPrettyNameinstead ofDisplayName. One line, no re-import. - Flow field fix (missing libraries) — in Rebuild Permission Index, add
&$top=5000toGet_Unique_Perm_Libraries's Uri so it stops capping out around 20-30 libraries. One line, no re-import. - Backfill name fix (optional) — same
DisplayName→PrettyNamebug as above, in Backfill File Plan. Only matters if you ever run Backfill again. - Register Existing Library (optional) — adds a second path through Create Library (App) for registering a library that already exists, without creating a new one. Needed for scrAdminCreateLibrary v1.9's new toggle.
- Import all four flows — Create Library (App), Backfill File Plan, Rebuild Permission Index, Manage Library Permissions. Test Create Library (App) manually before touching Power Apps.
- Data connections + App.OnStart — add both SharePoint lists and the flow as data sources, paste App.OnStart in full.
- All three live screens — delete and paste fresh (screens have had controls added since first built, so a partial paste over the top won't apply cleanly). Then scrManageLibraries and scrEditLibraries — brand new, paste as new blank screens, nothing to delete first. scrManagePermissions remains just a design, not built — see the Screens section.
- Navigation bar — select each screen in the Tree view, paste that screen's snippet as an add-on (not a full replacement). Nudge existing top controls down afterward to clear the bar.
1. SharePoint List: LibraryFilePlan
Location: your main site — https://modgovuk.sharepoint.com/teams/1598/.
LibraryFilePlan_list_template.csv — download this, then Site Contents → New → List → From CSV (or Excel) → upload it. SharePoint reads the embedded schema and creates every column with the correct type and Team's choices already filled in — no manual column-by-column setup. Delete the single placeholder row afterward, and rename the list to exactly LibraryFilePlan if it doesn't pick that up from the filename.
| Column name (exact, no spaces) | Type | Notes |
|---|---|---|
| Title | Single line of text | Built-in — the flow writes the library's pretty display name into it automatically |
| LibraryUrl | Single line of text | The real join key — a library's server-relative URL, survives display-name renames |
| Team | Choice | Top-level file-plan grouping. Fixed set — type in your known team names as the choices, no "Allow fill-in values" |
| SubTeam | Choice | Optional nested grouping. Tick "Allow fill-in values" — same pattern as the CAT column, so new sub-teams can be added on the fly |
| PrettyName | Single line of text | Optional override; falls back to live library Title if blank. Not "DisplayName" — SharePoint silently drops writes to a column with that exact name on this list (collides with a reserved sub-property on the two Person-or-Group columns, Created By/Modified By) |
| SiteUrl | Single line of text | Which of your sites the library is on |
| SortOrder | Number | Optional — controls order within a group |
| IsLimited | Yes/No | True if the library was created as Limited — lets you set conditional formatting on the list view to colour-code Limited rows |
_x0020_ internally, and the flows' requests need the plain names to match exactly.SharePoint List: PermissionIndex
Location: your main site, same as LibraryFilePlan.
PermissionIndex_list_template.csv — same idea: Site Contents → New → List → From CSV → upload, delete the placeholder row, rename to exactly PermissionIndex if needed.
| Column name (exact, no spaces) | Type | Notes |
|---|---|---|
| Title | Single line of text | Built-in — set to the same value as GroupName, so the list is readable at a glance |
| LibraryUrl | Single line of text | Which library this permission row belongs to — joins back to LibraryFilePlan.LibraryUrl |
| LibraryTitle | Single line of text | Display name, so search results don't need a lookup back to LibraryFilePlan |
| GroupName | Single line of text | The actual SG_/DG_ group name (or username) this row represents — what the group-search screen filters on |
| PrincipalType | Single line of text | "SharePoint Group", "Security Group", or "User" |
| PermissionLevel | Single line of text | e.g. "Full Control", "Contribute", "Read" |
| NestedUnderGroup | Single line of text | Blank for a group/user granted directly on the library. If this row is a DG_ AD group found nested inside an SG_ group, this holds that SG_ group's name |
LibraryFilePlan.2. Flow Field Fix: Create_FilePlan_Row
In Power Automate, open Create Library (App) → Edit → click Create_FilePlan_Row to expand it → click into its Body box → select all, delete → paste this in as the replacement → Save. Existing action, existing field, no re-import needed.
{
"__metadata": { "type": "SP.Data.LibraryFilePlanListItem" },
"Title": "@{triggerBody()?['DisplayName']}",
"LibraryUrl": "@{body('Get_New_Library_Url')?['d']?['RootFolder']?['ServerRelativeUrl']}",
"Team": "@{triggerBody()?['Team']}",
"SubTeam": "@{triggerBody()?['SubTeam']}",
"PrettyName": "@{triggerBody()?['DisplayName']}",
"SiteUrl": "@{triggerBody()?['SiteUrl']}",
"IsLimited": @{if(equals(triggerBody()?['IsLimited'], true), 'true', 'false')}
}
DisplayName → PrettyName). The value is still populated from triggerBody()?['DisplayName'] — that's the Power Apps trigger's own parameter name, unrelated to the SharePoint column, and it hasn't changed.2b. Flow Field Fix: Get_Unique_Perm_Libraries (missing libraries bug)
Cause: this action asks SharePoint for every library with unique permissions, but never told it how many to return. SharePoint's _api/web/lists endpoint defaults to a page size of around 100 items per response and doesn't automatically continue to the next page — with enough lists on the site (including hidden/system ones counted before the filter narrows them down), the real Limited libraries near the end of that list never got reached, so the index silently stopped at whatever fit in the first page.
In Power Automate, open Rebuild Permission Index → Edit → click Get_Unique_Perm_Libraries to expand it → click into its Uri box → add &$top=5000 to the end → Save. Existing action, one field, no re-import needed.
_api/web/lists?$filter=BaseTemplate eq 101 and Hidden eq false and HasUniqueRoleAssignments eq true&$select=Title,Id,RootFolder/ServerRelativeUrl&$expand=RootFolder&$top=5000
2c. Flow Field Fix: Backfill File Plan (same DisplayName bug as 2)
Cause: found 25 Sep 2026 while investigating why some libraries showed raw, ugly names in scrFilePlan instead of a proper Pretty Name. This flow's own Create_FilePlan_Row action was writing to DisplayName, not PrettyName — the exact same silently-dropped-write bug fixed in section 2 above for Create Library (App), but never applied here since the two flows only share a pattern, not the actual action. Any library that was ever registered by running Backfill (rather than created through the app's own Create Library button) has no Pretty Name at all as a result.
In Power Automate, open Backfill File Plan → Edit → find the Create_FilePlan_Row action inside the If_Not_Already_Tracked condition, inside Apply_to_each_Library → click into its Body box → select all, delete → paste this in as the replacement → Save.
{
"__metadata": { "type": "SP.Data.LibraryFilePlanListItem" },
"Title": "@{items('Apply_to_each_Library')?['Title']}",
"LibraryUrl": "@{items('Apply_to_each_Library')?['Url']}",
"PrettyName": "@{items('Apply_to_each_Library')?['Title']}",
"SiteUrl": "@{coalesce(triggerBody()?['SiteUrl'], 'https://modgovuk.sharepoint.com/teams/1598')}",
"Team": "",
"SubTeam": "",
"IsLimited": @{if(equals(items('Apply_to_each_Library')?['Unique'], true), 'true', 'false')}
}
LibraryFilePlan right now. For those, export the list, fill in Pretty Name yourself (AI-assisted or by hand), re-import, then refresh colFilePlan in the app (reload, or hit any button that re-pulls it).2d. Flow Surgery: Create Library (App) — Register Existing Library
Needed for scrAdminCreateLibrary v1.9's new "Register an existing library only" toggle. This adds a second path through the same flow — nothing gets deleted or recreated, and no new flow needs registering as a data source in the app.
- Add two new trigger inputs. Open Create Library (App) → Edit → click the trigger (Power Apps) → under its inputs, add:
RegisterOnly(type Yes/No, not required) andExistingLibraryTitle(type Text, not required). - Add two "Initialize variable" actions right after the trigger, before
Select_Categories:varOutDisplayName(String, leave the value blank) andvarOutLibraryUrl(String, leave the value blank). - Add a Condition action right after those two, still before
Select_Categories. Set it to checkRegisterOnly is equal to true. - Move the existing chain into the "No" branch. Select
Select_CategoriesthroughCreate_FilePlan_Row(everything except the final Response action) — multi-select them (click the first, shift-click the last, or use the "..." menu → Select actions), Cut, then click inside the Condition's No branch and Paste. - At the end of the "No" branch (after
Create_FilePlan_Row), add two Set variable actions:Set varOutDisplayNametotriggerBody()?['DisplayName']Set varOutLibraryUrltobody('Get_New_Library_Url')?['d']?['RootFolder']?['ServerRelativeUrl']
- In the "Yes" branch, add these actions in order:
- An HTTP action (Send an HTTP request to SharePoint) named
Get_Existing_Library_Url: GET_api/web/lists/getbytitle('@{triggerBody()?['ExistingLibraryTitle']}')/RootFolder/ServerRelativeUrl, Site Address = the same dataset expression already used elsewhere in this flow (coalesce(triggerBody()?['SiteUrl'], 'https://modgovuk.sharepoint.com/teams/1598')). - Another HTTP GET,
Get_Existing_Row:_api/web/lists/getbytitle('LibraryFilePlan')/items?$filter=LibraryUrl eq '@{body('Get_Existing_Library_Url')?['d']?['ServerRelativeUrl']}'&$select=Id&$top=1— same dedupe check Backfill already uses. - A Condition,
If_Not_Already_Tracked:length(body('Get_Existing_Row')?['d']?['results'])is equal to0. - Inside its "Yes": an HTTP POST,
Create_FilePlan_Row_Register, same URI as the existingCreate_FilePlan_Row(_api/web/lists/getbytitle('LibraryFilePlan')/items), with this body:{ "__metadata": { "type": "SP.Data.LibraryFilePlanListItem" }, "Title": "@{triggerBody()?['ExistingLibraryTitle']}", "LibraryUrl": "@{body('Get_Existing_Library_Url')?['d']?['ServerRelativeUrl']}", "PrettyName": "@{coalesce(triggerBody()?['DisplayName'], triggerBody()?['ExistingLibraryTitle'])}", "Team": "@{triggerBody()?['Team']}", "SubTeam": "@{triggerBody()?['SubTeam']}", "SiteUrl": "@{coalesce(triggerBody()?['SiteUrl'], 'https://modgovuk.sharepoint.com/teams/1598')}", "IsLimited": @{if(equals(triggerBody()?['IsLimited'], true), 'true', 'false')} } - Still inside the "Yes" branch of
If_Not_Already_Tracked, after that action:Set varOutDisplayNametotriggerBody()?['ExistingLibraryTitle'], andSet varOutLibraryUrltobody('Get_Existing_Library_Url')?['d']?['ServerRelativeUrl']. - In the "No" branch of
If_Not_Already_Tracked(already registered): the same two Set variable actions, so the flow still responds cleanly instead of leaving the variables blank.
- An HTTP action (Send an HTTP request to SharePoint) named
- Move the existing
Responseaction out from directly afterCreate_FilePlan_Rowto sit after the whole Condition block (both branches). Update its body to read from the variables instead of the old direct references:{ "Success": true, "DisplayName": "@{variables('varOutDisplayName')}", "LibraryUrl": "@{variables('varOutLibraryUrl')}" } - Save. Test both paths manually before touching Power Apps: once with
RegisterOnlyoff (normal creation, unchanged), once with it on and a real existing library's title.
Terminate action in the "Yes" branch: a flow that never reaches its own Response action can surface as an error on the Power Apps side. Routing both paths through one shared Response, fed by variables set in whichever branch actually ran, is the same fix pattern already proven elsewhere in this app family for "flow needs one shared response after branching logic."2e. Flow Field Fix + New Column: Open Permissions Link
Want: a button on scrGroupSearch's library rows that opens that specific library's real SharePoint permissions page directly, in a new tab — no more hunting for it manually.
How it's possible: SharePoint's classic "Permissions for this document library" page is {site}/_layouts/15/user.aspx?obj={GUID}&List={GUID}, where the GUID is that specific list's internal ID. Confirmed 26 Sep 2026 from a real URL Allan copied out of Studio. Nothing we currently store has that GUID — LibraryFilePlan and PermissionIndex both only have the server-relative URL — but FP - Rebuild Permission Index already fetches it for every library it scans (its Get_Unique_Perm_Libraries step selects Id alongside Title and the URL), it just never saves it anywhere. Cheapest fix: capture it.
- Add one column to
PermissionIndex: SharePoint list settings → Create column →LibraryId, type Single line of text, Not Required. - Fix
Select_Librariesfirst. This action runs right afterGet_Unique_Perm_Librariesand reshapes its results down to justTitleandUrlbeforeApply_to_each_Libraryever sees them —Idgets fetched from SharePoint one step earlier, then thrown away here. OpenSelect_Libraries→ its Select box already has Title and Url mapped → add a third field:Id → @item()?['Id']
- Edit the flow. Power Automate → FP - Rebuild Permission Index → Edit → find both
Create_Index_RowandCreate_Nested_Index_Row(insideApply_to_each_Assignment→If_Not_System_Group, and inside the nestedIf_SharePoint_Group→Apply_to_each_Nested_Member) → click into each one's Body box → add one new line before the closing brace:Same expression in both actions —"LibraryId": "@{items('Apply_to_each_Library')?['Id']}"Apply_to_each_Libraryis the outer loop both actions already run inside, so itsIdis available in each without any new lookup, once step 2 above has put it there. - Save the flow.
items('Apply_to_each_Library')?['Id'] without checking that Select_Libraries actually carries an Id field through. It doesn't, by default, so the column came back completely blank even though the flow ran with no errors (a missing field via ?['Id'] just resolves to nothing, it doesn't fail). Confirmed and fixed 26 Sep 2026 after Allan hit exactly this.PermissionIndex from scratch every run, existing rows won't get LibraryId retroactively just from the flow change — you need to run it again (the Refresh Index button on scrGroupSearch, or Rebuild Permission Index on scrAdminCreateLibrary) after saving, for every row to actually carry the new field.Once the column and flow edit are done and a rebuild's been run, tell me and I'll add the actual button to scrGroupSearch — the Power Fx side is simple, a Launch() call using the new LibraryId field:
Launch(varTenantHost & "/_layouts/15/user.aspx?obj={" & ThisItem.LibraryId & "}&List={" & ThisItem.LibraryId & "}")
3. Download & Import Flows
CreateLibraryApp_flow_import.zip — creates a library, registers it into LibraryFilePlan. The core flow everything else builds on.
BackfillFilePlan_flow_import.zip — one-time scan of every existing library on the site, registers anything not already tracked.
RebuildPermissionIndex_flow_import.zip — scans libraries with broken permissions and builds a fast, searchable index of their groups.
ManageLibraryPermissions_flow_import.zip — lists, adds, or removes a group directly on a chosen library, called live from the Admin screen.
DeleteLibrariesApp_flow_import.zip — checks each library for files, deletes the ones that are safe to delete (or all of them if overridden), and cleans up their LibraryFilePlan/PermissionIndex rows. Called live from the Manage Libraries screen.
All five: Power Automate → My flows → Import → Import Package (Legacy).
What Create Library (App) does
Triggered from Power Apps (or manually, for testing). Per library:
- Creates the library (
BaseTemplate 101) with the correct sensitivity label for Normal vs Limited - Adds a CAT choice column (fill-in-your-own enabled) and a Status choice column (Draft / Under Review / Reviewed / Published / Final / Superseded / Expired)
- Creates WiP view (items without a retention label) and Records view (items with one), both grouped by CAT
- Renames the library's Title to the pretty display name
- If Limited: breaks role inheritance completely, grants SG_Cadets-Admin-Ltd_MEM and SG_IHUB Full Control (fixed — nothing to fill in on the form, since a separate team owns creating/adding any other group later), then removes the flow-runner's own account (SharePoint auto-adds whoever breaks inheritance as a lockout safety net; the flow strips that back out)
- Registers the new library into
LibraryFilePlan
Site URL defaults to the main site if left blank — every action falls back to it via coalesce().
Testing Create Library (App)
Power Automate → My flows → Create Library (App) → Test → Manually.
| Field | First test — plain | Second test — Limited |
|---|---|---|
| Site URL | leave blank | leave blank |
| Technical Name | ZZTest_Library | ZZTest_LTD_Library |
| Display Name | ZZ Test Library | ZZ Test LTD Library |
| Is Limited | false / off | true / on |
| Team | Test | Test |
| SubTeam, Categories | leave blank | leave blank |
First test: check the library exists with CAT + Status columns and WiP/Records views, its Title shows the pretty name, and a new row appeared in LibraryFilePlan with PrettyName populated.
SG_Cadets-Admin-Ltd_MEM and SG_IHUB are there — no inherited groups, no extra groups, no direct user entry for whoever ran the flow.LibraryFilePlan once both tests pass.Backfill Existing Libraries
LibraryFilePlan only tracks libraries created after this system existed. This flow seeds it from what already exists: scans every real document library (skipping SharePoint's own system libraries), and for anything not already tracked, adds a row with LibraryUrl, PrettyName, Title, SiteUrl filled in automatically, and IsLimited guessed from whether the library already has broken permission inheritance. Team, SubTeam are left blank — fill those in by hand afterward via SharePoint's grid/quick-edit view. Safe to run more than once — only adds what's missing.
Rebuild Permission Index
Power Apps can't read SharePoint permissions directly, and a search screen can't afford to live-query every library's permissions on every keystroke. This flow scans permissions once and writes a flat, fast-searchable copy into PermissionIndex; the group-search screen just filters that list.
Only scans libraries with broken/unique permissions (mostly your Limited libraries) — Normal libraries all inherit the exact same site-wide permissions, so indexing each individually would be pure noise. If a group is a native SharePoint Group, it also looks one level inside that group's own membership — this is how a nested DG_... AD group shows up in the index too, without needing Microsoft Graph.
PermissionIndex and rebuilds it from scratch every run — intentional, avoids stale/duplicate rows.Manage Library Permissions
This is the write counterpart to Rebuild Permission Index / Group Search, which are both read-only snapshots. This flow is called live, on demand, from the Admin screen — one flow, one trigger, an Action field ("List" / "Add" / "Remove") decides what it does:
- List — live role assignments for one library, straight from SharePoint (not the cached index), so it's always current
- Add — grants a SharePoint group (
SG_...) a permission level on that library, reusing the exact sameGet_Group_Id → Get_Role_Def_Id → Add_Role_Assignmentpattern already proven in Create Library (App) - Remove — removes a group's entire access to that library in one call (
roleassignments/getbyprincipalid(id)with an HTTP DELETE), rather than needing to know which specific permission level to unwind
SG_...) directly on a library — matches how every Limited library in this system is actually set up: a DG_... AD group is never granted directly, it's always nested inside an SG_... wrapper group. If a genuinely new AD group needs adding, the other team creates it and its SG_ wrapper first, same as always — this screen doesn't change that workflow, just removes the "someone has to do it by hand in native SharePoint" step.Delete Libraries (App)
Called from the Manage Libraries screen with a JSON array of library URLs, an Action (currently always "Delete" from the app — "Check" is accepted too but every branch other than Delete just reports counts and changes nothing, so it's there if you ever want a preview-only call), and a Force flag.
Per library: gets its live ItemCount via _api/web/GetList(url) (server-relative URL, not title — the one join key in this whole system guaranteed not to drift if someone renames a library directly in SharePoint). If Force is false and ItemCount > 0, that library is skipped and reported back, untouched. Otherwise it deletes the document library itself, its LibraryFilePlan row, and every PermissionIndex row that pointed at it.
4. Building the Power App
Create a new blank canvas app in Power Apps Studio.
Data connections
Data panel → Add data → add these before pasting any screen YAML:
'LibraryFilePlan' (SharePoint → your main site → LibraryFilePlan) 'PermissionIndex' (SharePoint → your main site → PermissionIndex) 'Create Library (App)' (search Power Automate flows in Add data) 'Rebuild Permission Index' (search Power Automate flows in Add data) 'Manage Library Permissions' (search Power Automate flows in Add data) 'Delete Libraries (App)' (search Power Automate flows in Add data)
App.OnStart
Select the App object (top of the tree, not any screen) → OnStart property → paste this in full:
FlagSaysLimited to colLibraryGroups — cross-references each library's real permission-index membership against LibraryFilePlan.IsLimited, feeding the new dual-signal padlock/warning indicator on scrGroupSearch v3.9. Also initialises varIndexRefreshing, a busy-state flag for that screen's new Refresh Index button (reuses the existing varIndexRunError variable for error tracking, already declared below, same one scrAdminCreateLibrary's Rebuild Permission Index button uses).Set(varTenantHost, "https://modgovuk.sharepoint.com");
ClearCollect(colFilePlan, 'LibraryFilePlan');
ClearCollect(
colFilePlanFiltered,
AddColumns(colFilePlan, TeamName, If(IsBlank(Team.Value) || Team.Value = "", "Uncategorised", Team.Value), SubTeamName, Coalesce(SubTeam.Value, ""), EffectiveName, If(IsBlank(PrettyName) || PrettyName = "", Title, PrettyName))
);
ClearCollect(
colTeamGroups,
Sort(
AddColumns(
GroupBy(colFilePlanFiltered, TeamName, GroupedItems),
ItemCount, CountRows(GroupedItems),
TeamSortKey, If(TeamName = "Uncategorised", "zzz_" & TeamName, TeamName)
),
TeamSortKey,
SortOrder.Ascending
)
);
ClearCollect(
colSubTeamGroups,
AddColumns(
GroupBy(
Filter(colFilePlanFiltered, !IsBlank(SubTeamName) && SubTeamName <> ""),
TeamName, SubTeamName, GroupedItems
),
ItemCount, CountRows(GroupedItems)
)
);
Clear(colFlatSource);
Collect(colFlatSource, AddColumns(colTeamGroups, RowType, "Team", SortKey, TeamSortKey, SubSortKey, "", RankInGroup, 0));
Collect(colFlatSource, AddColumns(colSubTeamGroups, RowType, "SubTeam", SortKey, If(TeamName = "Uncategorised", "zzz_" & TeamName, TeamName), SubSortKey, SubTeamName, RankInGroup, 1));
Collect(colFlatSource, AddColumns(colFilePlanFiltered, RowType, "Library", SortKey, If(TeamName = "Uncategorised", "zzz_" & TeamName, TeamName), SubSortKey, If(IsBlank(SubTeamName) || SubTeamName = "", "", SubTeamName), RankInGroup, If(IsBlank(SubTeamName) || SubTeamName = "", 1, 2)));
ClearCollect(colExpandedKeys, Filter(Table({TeamName: "seed"}), false));
ClearCollect(colExpandedSubTeams, Filter(Table({SubTeamKey: "seed"}), false));
Set(varIsAdmin, Lower(User().Email) in ["allan.hamilton775@mod.gov.uk", "dan.smith391@mod.gov.uk"]);
ClearCollect(colQueue, Filter(Table({TechnicalName: "seed", DisplayName: "", Team: "", SubTeam: "", IsLimited: false, Categories: "", RegisterOnly: false, ExistingLibraryTitle: ""}), false));
Set(varQueueRunning, false);
Set(varQueueTotal, 0);
Set(varQueueDone, 0);
Set(varIndexRunError, false);
Set(varIndexRefreshing, false);
ClearCollect(colPermissionIndex, Filter('PermissionIndex', PermissionLevel <> "Limited Access" && PrincipalType <> "User"));
ClearCollect(colPermissionFiltered, colPermissionIndex);
ClearCollect(colLibraryGroups, Sort(AddColumns(GroupBy(colPermissionFiltered, LibraryUrl, LibraryTitle, GroupedItems), ItemCount, CountRows(GroupedItems), FlagSaysLimited, With({_lu: LibraryUrl}, Coalesce(LookUp(colFilePlan, LibraryUrl = _lu, IsLimited), false))), LibraryTitle, SortOrder.Ascending));
Clear(colPermFlatSource);
Collect(colPermFlatSource, AddColumns(colLibraryGroups, RowType, "Library", SortKey, LibraryUrl, RankInGroup, 0, GroupSortKey, "", DirectGroupCount, With({_lu: LibraryUrl}, CountRows(Filter(colPermissionFiltered, LibraryUrl = _lu && (IsBlank(NestedUnderGroup) || NestedUnderGroup = ""))))));
Collect(colPermFlatSource, AddColumns(Filter(colPermissionFiltered, IsBlank(NestedUnderGroup) || NestedUnderGroup = ""), RowType, "Group", SortKey, LibraryUrl, RankInGroup, 1, GroupSortKey, GroupName, MemberCount, With({_gn: GroupName, _lu: LibraryUrl}, CountRows(Filter(colPermissionFiltered, NestedUnderGroup = _gn && LibraryUrl = _lu)))));
Collect(colPermFlatSource, AddColumns(Filter(colPermissionFiltered, !IsBlank(NestedUnderGroup) && NestedUnderGroup <> ""), RowType, "Member", SortKey, LibraryUrl, RankInGroup, 2, GroupSortKey, NestedUnderGroup));
ClearCollect(colExpandedLibs, Filter(Table({LibraryUrl: "seed"}), false));
ClearCollect(colExpandedGroups, Filter(Table({GroupKey: "seed"}), false));
ClearCollect(colSelectedLibraries, Filter(Table({LibraryUrl: "seed"}), false));
ClearCollect(colManageFiltered, colFilePlanFiltered);
ClearCollect(colEditQueue, Filter(colFilePlanFiltered, false));
ClearCollect(colDeleteSkipped, Filter(Table({LibraryUrl: "seed", Title: "seed", ItemCount: 0, Deleted: false}), false));
Set(varDeleteBusy, false);
Set(varDeleteStatus, "");
Set(varConfirmOverrideVisible, false);
Set(varDeletedCount, 0);
Set(varEditSaving, false);
Set(varEditSaveStatus, "")
EffectiveName is PrettyName when filled in, otherwise falls back to Title — self-heals every library missing a pretty name, no data migration needed. varIsAdmin gates the Admin screen — edit the email list directly in this line if who's authorized ever changes. colPermissionIndex/colPermissionFiltered feed the Group Search screen — make sure you've run Rebuild Permission Index at least once first, or this loads empty.5. Screens
For each: delete the existing screen if you already have one pasted, New screen (blank) → open the link → Copy YAML → paste. Rename the screen to match the name shown.
Read-only browse screen — search box, Team → SubTeam → Library collapsible accordion, Open and icon-only Copy buttons per library, small padlock icon + red/salmon row tint on Limited libraries.
v2.10 — 26 Sep 2026 — draft, not yet pastedRestricted to the admin email list. Fill in a library's details, Add to Queue as many times as needed, then Run Queue creates them all sequentially, with a progress overlay. New toggle lets you register an existing library instead of creating one. Also has a Rebuild Permission Index button with a staleness indicator.
v1.10 — 26 Sep 2026 — draft, not yet pastedSearch by SG_/DG_ group name or library name. Results grouped by library in a collapsible accordion, same pattern as scrFilePlan. Reads the precomputed PermissionIndex list — instant, no live SharePoint crawl. New: red padlock/purple warning dual-signal LTD indicator per library (flag vs. real permissions), plus a Refresh Index button that reruns FP - Rebuild Permission Index on demand.
Ready to build
These two are newly built from scratch (25 Sep 2026), rebuilt from an old August design that was never actually pasted into Studio. Paste as a brand new blank screen — there's nothing existing to delete first. Nav bar is already included in the paste, no separate add-on step needed.
Restricted to admins. Tick libraries, bulk-delete them (empty ones go straight away, non-empty ones get an override prompt) or send the selection to the edit cart.
v2.3 — 26 Sep 2026 — v2.1 errored on paste, see belowRestricted to admins. The edit cart — one stacked form per library sent over from Manage Libraries, change whatever's needed on each (including Limited access, now editable here), Save All applies every change in one pass.
v2.2 — 26 Sep 2026 — new, not yet pasted (v2.0 errored on paste, see below)Designed, never built
This one still doesn't exist in Studio, no current plan to build it. The YAML below is a design snapshot from August 2026, never confirmed live.
.Run() call has no true non-blocking mode (a slow response is caught and shown as a friendly message rather than a raw error, but the flow itself always runs to completion on its own regardless). The status line above it shows how long ago the index was last rebuilt, and flags how many Limited libraries have been created since — both computed live from data already loaded, no extra flow or list needed. v1.7 (draft): after Run Queue finishes, the refreshed file-plan collections now use the same "Uncategorised" grouping as scrFilePlan v2.7, so a newly-created library with no Team assigned doesn't fall back to a blank grouping until the next full app reload. v1.8: same update again for the SubTeam grouping tier added in scrFilePlan v2.8, for the same reason. v1.9 adds a "Register an existing library only" toggle (default off — unticking swaps Technical Name for an Existing Library Title field, hides Categories since no columns get created on this path), a "Manage Libraries" nav button, and disables the dead "Manage Permissions" nav button this screen's own copy had never had fixed. Needs the matching Create Library flow change described in the Flows section below — without it, ticking the box adds a queue entry but Run Queue won't actually register anything.RowType and a second expand-tracking collection (colExpandedGroups) rather than nested galleries. v3.1 fixed a real sort-order bug where member rows sorted after every group in a library instead of clustering under their own parent group — the sort's column priority had rank ahead of the group-clustering key instead of after it. v3.2 moved the counts directly into each chevron instead of a separate pill: 4 > on a Library row (how many groups directly granted), (2) > on a Group row (how many nested members) — visible whether the row is expanded or collapsed. v3.4 split the count back out into its own label and replaced the text chevron with a proper vector icon; added hover/press feedback on both Library and Group rows (previously gave no visual response); added a "Copy link" button on Library rows to match scrFilePlan; and added a friendly message when a search matches nothing. v3.5 fixed the same two bugs found on scrFilePlan v2.4: opaque hover fills blacking out row text instead of tinting it, and real library titles with underscores wrapping mid-word and overflowing into the row below (now shown with spaces, row made taller). v3.6 is a draft, not yet pasted: carries over the same full-width layout and icon-button treatment Allan applied to scrFilePlan v2.6, and disables the same dead "Manage Permissions" nav button. v3.7 (also draft): Security Group rows now get a distinct amber tint and no chevron/expand affordance at all — they never have enumerable members in this data (only SharePoint Groups do), so a chevron implying they could be expanded was misleading. Needs the matching App.OnStart update (v3.9) pasted too, which filters out "Limited Access" noise rows and individual User rows at the source so every screen reading colPermissionIndex benefits automatically. v3.8 adds a "Manage Libraries" nav button now that screen actually exists.ThisItem.Title (the library's real current SharePoint Title) when calling the flow, not PrettyName/EffectiveName — SharePoint's getbytitle() needs the actual current Title, and Create Library (App) renames every library's Title to the pretty name as its last real step, so by the time a library shows up here, its Title already is the pretty name.Force: false on the first call — any empty library in your selection is deleted immediately, any non-empty one is skipped and reported back in one round trip, no separate "check" step needed first. If anything was skipped, an override panel lists exactly which ones and their item counts; "Delete Anyway" re-sends only those, this time with Force: true. Uses CheckBox@0.0.30 (per-row tick, read/write via its Checked property) and ModernButton's BasePaletteColor for the red delete buttons — Modern buttons don't have a direct Fill property, unlike Classic buttons used everywhere else in this app; light seed colours get overridden by Fluent 2's contrast rules, but the dark red used here renders correctly. Full-width layout and the same "Uncategorised"-aware collection rebuild used everywhere else in this app, so a delete here doesn't leave scrFilePlan showing stale groupings until next reload. Needs the "Delete Libraries" flow added as a data source before pasting. v2.1: same padlock-icon + red/salmon row tint treatment as scrFilePlan v2.9, replacing this screen's own LTD badge which had drifted to a different amber tone — now consistent across the app. v2.1 errored on paste, real errors, both fixed in v2.2: the flow reference was still the pre-rename name, 'Delete Libraries (App)' — confirmed current name is 'FP-DeleteLibraries(App)', now used in both .Run() calls. Also a genuine syntax bug: one branch of an If() wrapped a chain of statements in an extra pair of parentheses, which Power Fx doesn't support (parens group one expression, not a statement sequence) — removed, matching the plain semicolon-chain-with-no-wrapping-parens pattern already proven working elsewhere in this app.colEditQueue), shown here as one stacked card per library, all editable at once. Every field edit patches straight into that local collection as you type (no flow, no round trip); "Save All" is the only point anything actually writes to SharePoint, via a single ForAll + Patch. Library URL is deliberately not editable here — it's the join key this entire app relies on to survive renames, so changing it needs to stay a rare, manual, direct-in-SharePoint action, not a button. New: a "Limited access" tick box per card, so a wrongly-flagged LTD badge can actually be corrected now — note this only fixes the record, not real SharePoint permissions if those are genuinely mismatched. v2.0 errored on paste, real PA2108 errors, both fixed in v2.1: the dropdown's default-selection property was `DefaultSelectedItems` (a Combo box property, wrong control) instead of `ModernDropdown`'s actual `Default`; and the Limited-access control was a `Toggle`, which doesn't support any change-event property at all in this environment (confirmed — every real Toggle usage in the wider guide library only ever reads `.Checked` on demand, never reacts to it) — swapped for a `CheckBox`, same proven pattern already live in scrManageLibraries' own row-select tick boxes.Issues
Found a problem? Report it here.