← Guide Library

Library & File Plan Manager v4.3

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.

Built 13 Aug 2026 10:23 BST

Every purple line like this one, directly above a copy box, shows the page version and the exact local time that specific box's content was last changed. If a copy still errors after re-pasting, check this stamp against what's shown before assuming the fix didn't take — refresh the page fully first.

Action Checklist — Do These In Order

  1. SharePoint lists — create LibraryFilePlan and PermissionIndex via the CSV templates (fastest — creates every column for you), or manually per the schema tables. Column is PrettyName, not DisplayName — SharePoint silently drops writes to that name on this list.
  2. Flow field fix — in Create Library (App), edit Create_FilePlan_Row's Body so it writes PrettyName instead of DisplayName. One line, no re-import.
  3. Flow field fix (missing libraries) — in Rebuild Permission Index, add &$top=5000 to Get_Unique_Perm_Libraries's Uri so it stops capping out around 20-30 libraries. One line, no re-import.
  4. 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.
  5. Data connections + App.OnStart — add both SharePoint lists and the flow as data sources, paste App.OnStart in full.
  6. All six screens — delete and paste fresh (screens have had controls added since first built, so a partial paste over the top won't apply cleanly).
  7. 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.
Current state: all four planned screens are built. File Plan and Admin: Create Library are tested and working. Group Search and Manage Permissions are new — not yet tested. This was the last screen in the original plan.

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)TypeNotes
TitleSingle line of textBuilt-in — the flow writes the library's pretty display name into it automatically
LibraryUrlSingle line of textThe real join key — a library's server-relative URL, survives display-name renames
TeamChoiceTop-level file-plan grouping. Fixed set — type in your known team names as the choices, no "Allow fill-in values"
SubTeamChoiceOptional nested grouping. Tick "Allow fill-in values" — same pattern as the CAT column, so new sub-teams can be added on the fly
PrettyNameSingle line of textOptional 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)
SiteUrlSingle line of textWhich of your sites the library is on
SortOrderNumberOptional — controls order within a group
IsLimitedYes/NoTrue if the library was created as Limited — lets you set conditional formatting on the list view to colour-code Limited rows
Mark every column Not Required. Type names exactly as shown, no spaces — SharePoint silently converts spaces to _x0020_ internally, and the flows' requests need the plain names to match exactly.
The CSV was built by hand to match the schema format seen in a real export from this exact list (same embedded-schema mechanism, not guessed) but hasn't been round-tripped through SharePoint's import wizard directly. If "From CSV" doesn't pick up the column types correctly, fall back to creating the columns manually per the table above — same end result either way.

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)TypeNotes
TitleSingle line of textBuilt-in — set to the same value as GroupName, so the list is readable at a glance
LibraryUrlSingle line of textWhich library this permission row belongs to — joins back to LibraryFilePlan.LibraryUrl
LibraryTitleSingle line of textDisplay name, so search results don't need a lookup back to LibraryFilePlan
GroupNameSingle line of textThe actual SG_/DG_ group name (or username) this row represents — what the group-search screen filters on
PrincipalTypeSingle line of text"SharePoint Group", "Security Group", or "User"
PermissionLevelSingle line of texte.g. "Full Control", "Contribute", "Read"
NestedUnderGroupSingle line of textBlank 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
Mark every column Not Required, exact names, no spaces — same rule as 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.

Saving the flow is not enough on its own — a Power App that already calls this flow keeps running the old logic until its own connection to the flow is refreshed. After saving, go into Power Apps Studio → Data panel → find Create Library (App) under your data sources → refresh it (remove and re-add if there's no direct refresh option). Do this after every flow edit, not just this one.

v2.6 — Updated 12 Aug 2026 08:30 BST

{
  "__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')}
}
Only the SharePoint field name changed (DisplayNamePrettyName). 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.

v4.2 — Updated 13 Aug 2026 09:26 BST

_api/web/lists?$filter=BaseTemplate eq 101 and Hidden eq false and HasUniqueRoleAssignments eq true&$select=Title,Id,RootFolder/ServerRelativeUrl&$expand=RootFolder&$top=5000
This changes the flow's own logic, same as any other flow edit — refresh Rebuild Permission Index's connection in the Power Apps Data panel afterward, then run the Rebuild button on the Admin screen once to repopulate the index with every library this time.
The downloadable flow zip for a fresh install already has this fix baked in — this section is only needed if you imported before 13 Aug 2026.

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:

  1. Creates the library (BaseTemplate 101) with the correct sensitivity label for Normal vs Limited
  2. Adds a CAT choice column (fill-in-your-own enabled) and a Status choice column (Draft / Under Review / Reviewed / Published / Final / Superseded / Expired)
  3. Creates WiP view (items without a retention label) and Records view (items with one), both grouped by CAT
  4. Renames the library's Title to the pretty display name
  5. 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)
  6. 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.

FieldFirst test — plainSecond test — Limited
Site URLleave blankleave blank
Technical NameZZTest_LibraryZZTest_LTD_Library
Display NameZZ Test LibraryZZ Test LTD Library
Is Limitedfalse / offtrue / on
TeamTestTest
SubTeam, Categoriesleave blankleave 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.

Second test is the one part of the whole system that touches security directly. After it runs, manually open Library Settings → Permissions for that document library and confirm only 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.
Clean-up: delete both test libraries and their two rows from 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.

Scans your whole library list — with ~500 libraries, expect several minutes. Runs 20 at a time in parallel. Let it finish; don't re-run while it's still going.

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.

Deletes the entire contents of PermissionIndex and rebuilds it from scratch every run — intentional, avoids stale/duplicate rows.
Excludes SharePoint's own auto-generated "Limited Access System Group" — this isn't a real business group, it's internal bookkeeping SharePoint creates whenever anything inside a library (even a single folder or file) has broken permissions. Since it's technically a SharePoint Group, expanding its membership floods the index with individual named users nobody would search for by name. Confirmed live: a first run without this exclusion produced ~500 rows, nearly all of them this noise. Also excludes "System Account" wherever it shows up as a nested member — SharePoint's own internal service account, never a real permission grantee.

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:

Only manages native SharePoint groups (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.

There is no undo. A skipped (non-empty) library only gets deleted if the app is told to override — but once that override is sent, the flow deletes exactly what it's told to, no second check.

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:

v4.3 — Updated 13 Aug 2026 10:23 BST

'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:

v3.8 — Updated 13 Aug 2026 10:23 BST

Set(varTenantHost, "https://modgovuk.sharepoint.com");
ClearCollect(colFilePlan, 'LibraryFilePlan');
ClearCollect(
    colFilePlanFiltered,
    AddColumns(colFilePlan, TeamName, Team.Value, SubTeamName, Coalesce(SubTeam.Value, ""), EffectiveName, If(IsBlank(PrettyName) || PrettyName = "", Title, PrettyName))
);
ClearCollect(
    colTeamGroups,
    Sort(
        AddColumns(
            GroupBy(colFilePlanFiltered, TeamName, GroupedItems),
            ItemCount, CountRows(GroupedItems)
        ),
        TeamName,
        SortOrder.Ascending
    )
);
Clear(colFlatSource);
Collect(colFlatSource, AddColumns(colTeamGroups, RowType, "Team", SortKey, TeamName, RankInGroup, 0));
Collect(colFlatSource, AddColumns(colFilePlanFiltered, RowType, "Library", SortKey, TeamName, RankInGroup, 1));
ClearCollect(colExpandedKeys, Filter(Table({TeamName: "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: ""}), false));
Set(varQueueRunning, false);
Set(varQueueTotal, 0);
Set(varQueueDone, 0);
Set(varIndexRunError, false);
ClearCollect(colPermissionIndex, 'PermissionIndex');
ClearCollect(colPermissionFiltered, colPermissionIndex);
ClearCollect(colLibraryGroups, Sort(AddColumns(GroupBy(colPermissionFiltered, LibraryUrl, LibraryTitle, GroupedItems), ItemCount, CountRows(GroupedItems)), 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.

Screen 1
scrFilePlan
Read-only browse screen — search box, grouped-by-team collapsible list, Open and Copy link buttons per library, LTD badge on Limited libraries.
Copy YAML →
Screen 2
scrAdminCreateLibrary
Restricted 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. Also has a Rebuild Permission Index button with a staleness indicator.
Copy YAML →
Screen 3
scrGroupSearch
Search 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.
Copy YAML →
Screen 4
scrManagePermissions
Restricted to admins. Pick a Limited library, see its live current permissions, add or remove a SharePoint group directly — no more doing this by hand in native SharePoint.
Copy YAML →
Screen 5
scrManageLibraries
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.
Copy YAML →
Screen 6
scrEditLibraries
Restricted to admins. The edit cart — one stacked form per library sent over from Manage Libraries, change whatever's needed on each, Save All applies every change in one pass.
Copy YAML →
scrFilePlan: one single flat gallery with uniform row height (a Team/Library discriminator column decides what each row shows) rather than nested galleries. Search is instant and local, no spinner needed, and auto-expands every Team with a match.
scrAdminCreateLibrary: Sub-Team is free text rather than a suggestion dropdown (Modern Combobox has a known instability issue). The queue is entirely local until you hit Run. Run Queue disables and shows "Creating..." immediately on click. The Rebuild Permission Index button is fire-and-forget — it doesn't wait for the flow to finish or show progress, since the run duration is unpredictable and Power Apps' .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.
scrGroupSearch: only finds results for libraries that were in scope for the last Rebuild Permission Index run (broken/unique-permission libraries — see that flow's section above). A Normal library never shows up here since it just inherits site-wide permissions; that's expected, not a bug. Searching matches the group name itself, the library name, or (for a nested AD group) the SharePoint group it's nested inside. v3.0 rebuilt the results as a three-tier accordion — Library → directly-granted group (matches SharePoint's own native Permissions panel exactly) → nested member, each level collapsed by default. Still one flat gallery underneath, just a third 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.
scrManagePermissions: unlike Rebuild Permission Index's fire-and-forget button, Add/Remove here wait for the result and immediately re-load the list — a single group operation is fast enough that blocking is the better UX, and there's a real result (success or a specific error) worth showing rather than "check back later." Uses 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.
scrManageLibraries: the delete button always sends 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. First-time use of two controls new to this app: 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.
scrEditLibraries: the "edit cart" pattern — ticking libraries on scrManageLibraries and hitting Edit Selected copies them into a local collection (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.
v3.2 redesigned the counts: instead of a separate pill, the count now sits directly next to each chevron — 4 > on a Library row (how many groups directly granted, collapsed), (2) > on a Group row (how many nested members). If you manually added lblGroupMemberCount per an earlier version of this guide, delete it — it's superseded by this and no longer needed.

Add this small nav bar to each of the six screens. This is an add-on, not a full screen replacement: select the screen in the Tree view first, then paste. Four of the five buttons (everything except File Plan and Group Search) are gated by varIsAdmin, same check used everywhere else in this app.

The bar sits at Y: 0, Height: 48 — it will visually overlap each screen's existing Title label (which starts around Y: 24). Nudge your screen's existing top controls down by about 50px afterward, or accept the overlap if it doesn't bother you — this wasn't worth reissuing every screen's full YAML just to shift everything down.

v4.3 — Updated 13 Aug 2026 10:23 BST

Paste onto scrFilePlan

Screens:
  scrFilePlan:
    Children:
      - recNavBarBg:
          Control: Classic/Button@2.2.0
          Properties:
            Text: =""
            DisplayMode: =DisplayMode.View
            Fill: =RGBA(30, 30, 45, 1)
            HoverFill: =RGBA(30, 30, 45, 1)
            PressedFill: =RGBA(30, 30, 45, 1)
            BorderThickness: =0
            X: =0
            Y: =0
            Width: =1000
            Height: =48
      - btnNavFilePlan:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="File Plan"
            X: =16
            Y: =6
            Width: =140
            Height: =36
            OnSelect: |-
              =Navigate(scrFilePlan)
      - btnNavGroupSearch:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Group Search"
            X: =164
            Y: =6
            Width: =160
            Height: =36
            OnSelect: |-
              =Navigate(scrGroupSearch)
      - btnNavCreateLibrary:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Create Library"
            Visible: =varIsAdmin
            X: =332
            Y: =6
            Width: =160
            Height: =36
            OnSelect: |-
              =Navigate(scrAdminCreateLibrary)
      - btnNavManagePermissions:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Manage Permissions"
            Visible: =varIsAdmin
            X: =500
            Y: =6
            Width: =190
            Height: =36
            OnSelect: |-
              =Navigate(scrManagePermissions)
      - btnNavManageLibraries:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Manage Libraries"
            Visible: =varIsAdmin
            X: =698
            Y: =6
            Width: =170
            Height: =36
            OnSelect: |-
              =Navigate(scrManageLibraries)

Paste onto scrGroupSearch

Screens:
  scrGroupSearch:
    Children:
      - recNavBarBg:
          Control: Classic/Button@2.2.0
          Properties:
            Text: =""
            DisplayMode: =DisplayMode.View
            Fill: =RGBA(30, 30, 45, 1)
            HoverFill: =RGBA(30, 30, 45, 1)
            PressedFill: =RGBA(30, 30, 45, 1)
            BorderThickness: =0
            X: =0
            Y: =0
            Width: =1000
            Height: =48
      - btnNavFilePlan:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="File Plan"
            X: =16
            Y: =6
            Width: =140
            Height: =36
            OnSelect: |-
              =Navigate(scrFilePlan)
      - btnNavGroupSearch:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Group Search"
            X: =164
            Y: =6
            Width: =160
            Height: =36
            OnSelect: |-
              =Navigate(scrGroupSearch)
      - btnNavCreateLibrary:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Create Library"
            Visible: =varIsAdmin
            X: =332
            Y: =6
            Width: =160
            Height: =36
            OnSelect: |-
              =Navigate(scrAdminCreateLibrary)
      - btnNavManagePermissions:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Manage Permissions"
            Visible: =varIsAdmin
            X: =500
            Y: =6
            Width: =190
            Height: =36
            OnSelect: |-
              =Navigate(scrManagePermissions)
      - btnNavManageLibraries:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Manage Libraries"
            Visible: =varIsAdmin
            X: =698
            Y: =6
            Width: =170
            Height: =36
            OnSelect: |-
              =Navigate(scrManageLibraries)

Paste onto scrAdminCreateLibrary

Screens:
  scrAdminCreateLibrary:
    Children:
      - recNavBarBg:
          Control: Classic/Button@2.2.0
          Properties:
            Text: =""
            DisplayMode: =DisplayMode.View
            Fill: =RGBA(30, 30, 45, 1)
            HoverFill: =RGBA(30, 30, 45, 1)
            PressedFill: =RGBA(30, 30, 45, 1)
            BorderThickness: =0
            X: =0
            Y: =0
            Width: =1000
            Height: =48
      - btnNavFilePlan:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="File Plan"
            X: =16
            Y: =6
            Width: =140
            Height: =36
            OnSelect: |-
              =Navigate(scrFilePlan)
      - btnNavGroupSearch:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Group Search"
            X: =164
            Y: =6
            Width: =160
            Height: =36
            OnSelect: |-
              =Navigate(scrGroupSearch)
      - btnNavCreateLibrary:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Create Library"
            Visible: =varIsAdmin
            X: =332
            Y: =6
            Width: =160
            Height: =36
            OnSelect: |-
              =Navigate(scrAdminCreateLibrary)
      - btnNavManagePermissions:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Manage Permissions"
            Visible: =varIsAdmin
            X: =500
            Y: =6
            Width: =190
            Height: =36
            OnSelect: |-
              =Navigate(scrManagePermissions)
      - btnNavManageLibraries:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Manage Libraries"
            Visible: =varIsAdmin
            X: =698
            Y: =6
            Width: =170
            Height: =36
            OnSelect: |-
              =Navigate(scrManageLibraries)

Paste onto scrManagePermissions

Screens:
  scrManagePermissions:
    Children:
      - recNavBarBg:
          Control: Classic/Button@2.2.0
          Properties:
            Text: =""
            DisplayMode: =DisplayMode.View
            Fill: =RGBA(30, 30, 45, 1)
            HoverFill: =RGBA(30, 30, 45, 1)
            PressedFill: =RGBA(30, 30, 45, 1)
            BorderThickness: =0
            X: =0
            Y: =0
            Width: =1000
            Height: =48
      - btnNavFilePlan:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="File Plan"
            X: =16
            Y: =6
            Width: =140
            Height: =36
            OnSelect: |-
              =Navigate(scrFilePlan)
      - btnNavGroupSearch:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Group Search"
            X: =164
            Y: =6
            Width: =160
            Height: =36
            OnSelect: |-
              =Navigate(scrGroupSearch)
      - btnNavCreateLibrary:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Create Library"
            Visible: =varIsAdmin
            X: =332
            Y: =6
            Width: =160
            Height: =36
            OnSelect: |-
              =Navigate(scrAdminCreateLibrary)
      - btnNavManagePermissions:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Manage Permissions"
            Visible: =varIsAdmin
            X: =500
            Y: =6
            Width: =190
            Height: =36
            OnSelect: |-
              =Navigate(scrManagePermissions)
      - btnNavManageLibraries:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Manage Libraries"
            Visible: =varIsAdmin
            X: =698
            Y: =6
            Width: =170
            Height: =36
            OnSelect: |-
              =Navigate(scrManageLibraries)

Paste onto scrManageLibraries

Screens:
  scrManageLibraries:
    Children:
      - recNavBarBg:
          Control: Classic/Button@2.2.0
          Properties:
            Text: =""
            DisplayMode: =DisplayMode.View
            Fill: =RGBA(30, 30, 45, 1)
            HoverFill: =RGBA(30, 30, 45, 1)
            PressedFill: =RGBA(30, 30, 45, 1)
            BorderThickness: =0
            X: =0
            Y: =0
            Width: =1000
            Height: =48
      - btnNavFilePlan:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="File Plan"
            X: =16
            Y: =6
            Width: =140
            Height: =36
            OnSelect: |-
              =Navigate(scrFilePlan)
      - btnNavGroupSearch:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Group Search"
            X: =164
            Y: =6
            Width: =160
            Height: =36
            OnSelect: |-
              =Navigate(scrGroupSearch)
      - btnNavCreateLibrary:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Create Library"
            Visible: =varIsAdmin
            X: =332
            Y: =6
            Width: =160
            Height: =36
            OnSelect: |-
              =Navigate(scrAdminCreateLibrary)
      - btnNavManagePermissions:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Manage Permissions"
            Visible: =varIsAdmin
            X: =500
            Y: =6
            Width: =190
            Height: =36
            OnSelect: |-
              =Navigate(scrManagePermissions)
      - btnNavManageLibraries:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Manage Libraries"
            Visible: =varIsAdmin
            X: =698
            Y: =6
            Width: =170
            Height: =36
            OnSelect: |-
              =Navigate(scrManageLibraries)

Paste onto scrEditLibraries

Screens:
  scrEditLibraries:
    Children:
      - recNavBarBg:
          Control: Classic/Button@2.2.0
          Properties:
            Text: =""
            DisplayMode: =DisplayMode.View
            Fill: =RGBA(30, 30, 45, 1)
            HoverFill: =RGBA(30, 30, 45, 1)
            PressedFill: =RGBA(30, 30, 45, 1)
            BorderThickness: =0
            X: =0
            Y: =0
            Width: =1000
            Height: =48
      - btnNavFilePlan:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="File Plan"
            X: =16
            Y: =6
            Width: =140
            Height: =36
            OnSelect: |-
              =Navigate(scrFilePlan)
      - btnNavGroupSearch:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Group Search"
            X: =164
            Y: =6
            Width: =160
            Height: =36
            OnSelect: |-
              =Navigate(scrGroupSearch)
      - btnNavCreateLibrary:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Create Library"
            Visible: =varIsAdmin
            X: =332
            Y: =6
            Width: =160
            Height: =36
            OnSelect: |-
              =Navigate(scrAdminCreateLibrary)
      - btnNavManagePermissions:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Manage Permissions"
            Visible: =varIsAdmin
            X: =500
            Y: =6
            Width: =190
            Height: =36
            OnSelect: |-
              =Navigate(scrManagePermissions)
      - btnNavManageLibraries:
          Control: ModernButton@1.0.0
          Properties:
            Text: ="Manage Libraries"
            Visible: =varIsAdmin
            X: =698
            Y: =6
            Width: =170
            Height: =36
            OnSelect: |-
              =Navigate(scrManageLibraries)
Validated against the real Studio parser (Layer A of this Bible's own payaml-validate tool) — clean on all six, including zero unproven-property flags, since every control/property here has already been proven elsewhere in this app. One thing not independently confirmed: that Studio's paste, given a screen selected in the Tree view and a Screens: scrName: Children: [...] fragment on the clipboard, adds those as new children of that screen rather than something else. If it doesn't behave that way, fall back to inserting each control manually (Insert → the control type, then set the properties shown above) — same end result, just click-by-click instead of paste.

Issues

Found a problem? Report it here.