WP Manifestindependent plugin directory
manifest / users / blt-toolkit

BLT Toolkit

Switch to another user and back, change usernames, and edit roles and capabilities in a Simple or Advanced view, under one security model and one activity log.

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/s-fx-com/blt-toolkit/archive/refs/heads/main.zip
Updates itself. It ships its own updater (Plugin Update Checker), so new versions appear under Dashboard → Updates.

BLT Toolkit gives WordPress site administrators three user-management tools in one place, under one security model and one activity log: switch to another user's account and back, change a user's username, and create and edit roles and capabilities. It replaces User Switching, Username Changer and User Role Editor on S-FX-managed sites. The role editor has two views of the same data. Simple shows each content type (Posts, Pages and every custom post type) as one plain-English access level such as View, Author (own only) or Edit all, plus site-wide toggles grouped by area. Advanced is the full capability matrix. The three tools share one protection layer, so a delegated user manager can never switch into, rename or re-role someone more powerful than themselves, and every switch, rename and role change lands in one log that says who did it, including "acting as" when the change was made from a switched session. Everything works with no external service, no Cloudflare and no credentials.

Part of the BLT family of WordPress plugins by S-FX.com. It is deliberately narrow: two tables, no front end beyond a "Switch back" link and an optional username form, a REST namespace used only by its own admin screens, and no runtime dependencies.

Verification

Every phase of SPEC.md section 17 is built: protection for administrators, recovery mode, the activity log and role snapshots, switching, username changes (including bulk rename and the front-end form), the roles engine with the Advanced view, the role lifecycle and the user-level role features, Simple view with separate permissions for custom post types, the Tools screen, the privacy exporter and eraser, and the Site Health tests. Each phase's acceptance criteria were exercised over HTTP against real WordPress installs, and the evidence is in tasks/todo.md.

  • Verified: composer lint, composer phpcs and composer test (601 unit tests) clean; the POT matches the code; the release zip builds and passes verify-zip.sh; uninstall removes exactly what it should; a keyboard-only pass of the Roles screen in both views and of the front-end pieces. The SPEC 18 smoke matrix passed on PHP 7.4 and 8.3, WordPress 6.3 and 7.1, SQLite, MariaDB, MariaDB with Redis, plain permalinks, a Bedrock layout with DISALLOW_FILE_MODS, BLT Secure, BLT M365 WP SSO (switching), WooCommerce (both order storages), ACF and CPT UI, and a 10,000-user site (the Users screen within 10% of the plugin inactive at the default 20 rows; 100 rows stays above, see tasks/lessons.md).
  • Owed: Cloudflare APO (the SW-25 bypass and CF-Connecting-IP, on staging behind Cloudflare); the update path, which needs the first release (1.0.0) and then a second one to prove a site updates through the updater; a run on a real shared host (no exec(), WP-Cron only) and on a PHP 7.4 host from the release zip; Bricks and Gravity Forms with the real plugins (both were simulated); BLT M365 WP SSO giving SSO-created users the additional default roles (US-2).
  • Owner decisions still open: the menu placement and position (D4) and the front-end prefix (D14), listed under Decisions.

Requirements

  • WordPress 6.3 or later. The family default is 6.0; BLT Toolkit raises it for parity with the switching code it reimplements (User Switching 1.12.2 requires 6.3), which also avoids guards around switch_to_user_locale(), did_filter() and the command palette.
  • PHP 7.4 or later.
  • Single site only. Activation on multisite is refused with a notice (decision D1).
  • No external service, account or API key.

Install

  1. Download blt-toolkit-X.Y.Z.zip from the latest release at github.com/S-FX-com/BLT-Toolkit. Use the release asset, not the "Source code" archive. No release has been published yet: the first one, 1.0.0, is published by the merge that completes Phase 7.
  2. In WordPress go to Plugins > Add New Plugin > Upload Plugin, upload the zip and activate it.
  3. Open BLT Toolkit in the admin menu.

Updates arrive through the normal Plugins screen. The plugin checks GitHub once a day, and Check for Updates under BLT Toolkit > Settings > Advanced checks on demand. The repository is private, so a site needs a read-only token to see updates (see BLT_TOOLKIT_GITHUB_TOKEN under Constants). On a site where WordPress may not change files (DISALLOW_FILE_MODS, Bedrock), new versions arrive with the deployment and the screen says so.

Moving from User Switching, Username Changer and User Role Editor

  • BLT Toolkit can be activated while the old plugins are still active. A BLT module whose job an active legacy plugin already does stays off, and its card on Overview shows a Conflict badge naming the plugin to deactivate (GEN-1). Site Health says the same. The other modules work normally.
  • User Switching keeps nothing on the server. Switch back before deactivating it: its cookies mean nothing to BLT Toolkit.
  • User Role Editor keeps roles in WordPress's own roles option, so deactivating it loses no role data (GEN-3). Tools > Import from legacy plugins brings over its "other default roles", its per-role "hide admin bar" flags and its first-activation backup (as a snapshot that is never pruned), and flags its ure_* capabilities for optional removal. Run that import before you delete User Role Editor: deleting it (as opposed to deactivating it) removes those settings and the backup.
  • Username Changer: the same Tools tab copies the minimum length, the notification toggle and the email subject and message (as plain text; when Username Changer used its default text, BLT Toolkit keeps its own), and copies its audit log into the activity log, marked as imported, with each change's original time, who made it and its IP address. Its Allowed Roles can become the self-service capability on those roles (a role that denies it in BLT Toolkit keeps the deny), but that step starts off: Username Changer 3.3.1 never enforced Allowed Roles, so importing them turns self-service on for those roles for the first time, and the preview says so. The preview lists the email tags BLT Toolkit does not replace ({date}, {name}, {fullname}, {minlength}) and how many entries are older than the activity log keeps. More than 1,000 entries are copied as a background job you can leave running. Custom button labels and messages are not carried over; use translations.
  • The ure_* capabilities show on the Roles screen as orphaned, under From User Role Editor; the import tab removes them all from every role and user in one step, after a dry run and a snapshot, once User Role Editor is deactivated.
  • Every import is previewed first, only reads from the source, records what it imported so a second run duplicates nothing, and never deletes the source's data (SPEC 15).

What works out of the box

Default behavior (SPEC 1.1). SPEC 1.1 lists self-service and bulk rename as "off until enabled"; what they actually do out of the box is described below (see Decisions).

Area Default behavior
Switch Administrators can switch to any non-administrator and back. "Switch To" appears on the Users list and profiles; "Switch back" appears in the toolbar, notices, the login screen and a fixed link on the site.
Usernames Administrators can change any username from the user's profile. Changes take effect immediately, including on sites with a persistent object cache. Old author URLs redirect.
Roles Roles screen opens in Simple view: one access level per content type plus site-wide toggles. Advanced view edits every capability. Every role change is snapshotted first, and Tools > Snapshots restores any snapshot after showing what would change.
Protection Non-administrators can never edit, delete, rename, re-role or switch into an administrator, and can never grant a capability they do not hold.
Activity log Every switch, rename and role change is logged with who did it, including "acting as" when the change was made from a switched session. WordPress's personal data tools cover it.
Self-service Administrators can change their own username from their profile (activation gives them blt_change_own_username). Other roles get it only when it is switched on for them in Settings > Usernames.
Bulk rename On the Usernames screen for anyone with blt_change_usernames (administrators by default): download a template, upload a CSV, check the dry run, then the renames run in the background. There is no setting to turn it on or off.
Separate CPT permissions Off until enabled for a post type with Give {Type} separate permissions on its Simple view row.
Site Health Five BLT Toolkit tests on Tools > Site Health: the database tables, plugins BLT Toolkit replaces, separate permissions, recovery mode and the activity log.

Where to find it

Each screen carries its own capability, so the tools can be delegated separately. WordPress shows the BLT Toolkit menu to anyone who can reach at least one of them.

Screen Menu Capability
Overview BLT Toolkit manage_options
Roles BLT Toolkit > Roles, and Users > Roles blt_edit_roles
Usernames BLT Toolkit > Usernames blt_change_usernames
Activity BLT Toolkit > Activity blt_view_toolkit_log
Tools BLT Toolkit > Tools blt_reset_roles (the legacy import tab needs manage_options too)
Settings BLT Toolkit > Settings manage_options

Switching and username changes also appear where people already work: row actions on the Users list, a button and a field on the user's profile, and the toolbar.

Tools

BLT Toolkit > Tools (blt_reset_roles) has one tab per job, each a link with its own URL. Every tab previews what it would change with a dry run first, writes only after you confirm, takes a role snapshot before it writes, and is checked by the same protection rules as the Roles screen; opening a tab changes nothing.

Tab What it does
Snapshots Every role snapshot, newest first, with when, who, why, and how many roles and capabilities differ from today. View shows what changed since; Restore shows the diff and what Administrator keeps regardless (it is never locked out), then puts the roles back after a fresh snapshot, so a restore can itself be undone (SNAP-4).
Export and import Export chosen roles as a .json file (format blt-toolkit-roles version 1, with their role details and any separate post type permissions they use) and import such a file on another site: a preview per role (New, Changed, Unchanged or Cannot be imported, with its capability diff, and capabilities not registered on this site flagged), then Create, Merge capabilities, Replace capabilities or Skip for each (RO-44, RO-45).
Reset Reset built-in roles' capabilities (WordPress's defaults for the five built-in roles, custom roles untouched) or Full reset (exactly the roles of a fresh install; custom roles are deleted). Both list every change first and need the word RESET typed (RO-43).
Sync administrator Grants Administrator the registered capabilities it lacks, exactly the list the preview shows (RO-5). Needs the Roles module.
Import from legacy plugins Username Changer's and User Role Editor's settings and history (see Moving from User Switching, Username Changer and User Role Editor). Needs manage_options as well.

Shortcodes

[blt_toolkit_username_form]

Front-end form that lets signed-in users change their own username (UN-23). It is a real form that posts to admin-post.php and redirects back to the same page with the result, so it works with JavaScript disabled. It renders nothing for visitors and for users without blt_change_own_username; someone who holds it but changed their username within the Wait between changes period sees, instead of the form, when they can change it again (UN-21). Its stylesheet loads only on pages where a styled form renders. The same validation, cooldown, password rule (with its throttle of 5 wrong passwords in 15 minutes) and switched-session confirmation apply as on the profile screen, and the change is logged with source shortcode.

Attribute Default or Values Description
style styled (default), none styled loads blt-toolkit-front.css. none (or no-css) outputs the same markup with its .blt-tk-* classes and no stylesheet, for a theme that styles it itself. Any other value reads as styled.
class Empty Extra classes for the form's wrapper, separated by spaces, each sanitized with sanitize_html_class().

To change the markup, copy templates/username-form.php to your-theme/blt-toolkit/username-form.php (see Templates). Core's shortcode_atts_blt_toolkit_username_form filter can change the defaults for every form (HOOKS.md).

There is no block or Bricks element; Bricks renders the shortcode in its Shortcode element.

Customizing

Settings

BLT Toolkit > Settings (needs manage_options). Each tab is a link with its own URL. Labels are quoted as the screen shows them, with the stored key in the Notes where a hook or the code refers to it.

Tab Setting Default Notes
General Switch module, Usernames module, Roles module On A module that is off loads no code beyond its registry entry. A module also stays off while its legacy plugin is active (GEN-1) or when it is named in BLT_TOOLKIT_DISABLE_MODULES; the card says which. Protection and the log always run. The same toggles are on Overview.
Switching Show a "Switch back" link on the site On A fixed link, shown while switched when the toolbar is not showing, and after Switch Off (SW-6, SW-17; switch.footer_link).
Switching Offer "Switch Off" in the toolbar On For users with blt_switch_users (SW-14).
Switching Search for a user to switch to from the toolbar On Typeahead; without JavaScript it links to the Users screen (SW-20).
Switching Work with tools made for User Switching On SPEC SW-19's "Compatibility hooks" (switch.compat_hooks): also fires User Switching's actions and defines current_user_switched() (SW-19, D2). See HOOKS.md.
Switching WooCommerce On Only while WooCommerce is active (SW-22).
Switching Support Agent role Not created Create Support Agent role… opens a preview first: the role's three capabilities with their risk tier, and which of the site's roles a Support Agent could and could not switch into (administrators never, SEC-1; roles with a capability it lacks never, SEC-2). Because blt_switch_users is Critical, the role is created only after you type its name (SEC-10). Needs manage_options and blt_create_roles. Creating it takes a role snapshot first and is logged (SW-21). The button is gone once the role exists.
Usernames Minimum length 3 From 1 to 60; the maximum is always 60, the database column's limit (UN-2).
Usernames Names that cannot be used admin, administrator, root, sysadmin, webmaster One per line, compared case-insensitively and merged with WordPress's own list. Affects new names only (UN-4, D12).
Usernames Allow @ in usernames On Email-style usernames are common with SSO; a name that equals another user's email is always refused (UN-6, UN-7, D13).
Usernames Author URL Change it only when it was made from the old username (recommended) Or Always change it to match the new username, or Never change it (UN-13, D6).
Usernames Redirect old author URLs On 301 to the current author URL (UN-13a).
Usernames Sign the person out everywhere On Renaming someone else ends all their sessions; their old cookies stop working anyway. Renaming yourself keeps this session and ends your others (UN-14, D7).
Usernames Email the person about the change Off Plain text through wp_mail() (UN-15).
Usernames Email subject, Email message Blank (translated defaults) Tags {old_username}, {new_username}, {display_name}, {email}, {sitename}, {siteurl}, {loginurl}.
Usernames Who can change their own username Administrator (given on activation) One toggle per role. Saving gives or takes blt_change_own_username on that role, after a snapshot of the roles, checked by the protection rules and logged, so Simple view shows it too (UN-20).
Usernames Wait between changes 0 days Days between self-service changes (UN-21).
Usernames Ask for their current password Off Asked when people change their own username; after 5 wrong passwords within 15 minutes their username changes pause until the first of those is 15 minutes old (blt_toolkit_username_password_throttle). Users who sign in through SSO may not know a WordPress password (UN-22).
Roles Opens in Simple Each user's last choice is remembered (RS-1).
Roles Readable capability names On Plain-English labels; the raw name is always shown too (RO-12).
Roles Show deprecated capabilities Off level_0 to level_10 and other capabilities WordPress no longer uses (RO-22).
Roles Hide administrators from everyone else On In the Users list, its counts and the REST users collection (SEC-6, D5).
Roles Snapshots to keep 50 From 1 to 500, plus the baseline and any imported User Role Editor backup, which are never pruned (SNAP-3).
Roles Additional default roles None Roles every new user receives besides WordPress's default role, on every registration path: the registration form, Add New User (shown ticked, so they can be left out), REST, WP-CLI and single sign-on (US-2). A non-administrator who creates an account passes on only roles they could give. Administrator is never offered. Shown while the Roles module is on.
Activity log Keep entries for 365 days From 1 to 3,650; pruned daily in batches (LOG-4, D9).
Activity log Record IP addresses On From REMOTE_ADDR. CF-Connecting-IP is used only for requests that arrive from Cloudflare's published ranges, checked with a copy of the BLT family's range check (LOG-5).
Activity log Personal data requests What WordPress's privacy tools do with the log (see Privacy), with Export Personal Data and Erase Personal Data links for those who may use them.
Advanced Delete all plugin data on uninstall Off See Data.
Advanced GitHub token None Stored encrypted. Test connection asks GitHub for the repository with the token update checks would use; Remove token deletes a stored one. A token set by constant is shown as such and cannot be changed here.
Advanced Check for Updates Under Version: asks GitHub at once and ends on the Plugins screen with the result. Not shown where WordPress may not update plugins.
Advanced Restore administrator access, Switch modules off Under Recovery: the BLT_TOOLKIT_RECOVERY and BLT_TOOLKIT_DISABLE_MODULES lines for wp-config.php as code blocks, with a Set now badge while one is in effect.

A role's description is set under Role details on the Roles screen, its Hide the toolbar on the site flag there or in Simple view's Basics card, and separate permissions for a custom post type from that type's row in Simple view, not here.

Constants

Define these in wp-config.php.

Constant Effect
BLT_TOOLKIT_RECOVERY true restores an administrator role that lost the ability to manage the site. On the next admin request by a user who has the administrator role, BLT Toolkit takes a snapshot, re-grants the lockout-critical capabilities and BLT Toolkit's nine capabilities to administrator, logs it, and shows a notice until the constant is removed. It changes nothing else (SEC-14).
BLT_TOOLKIT_DISABLE_MODULES Comma-separated module keys (switch, usernames, roles) to turn off regardless of settings, for incident response. Protection and the log still load (SEC-15).
BLT_TOOLKIT_GITHUB_TOKEN A read-only GitHub token for the updater. Overrides the token stored in Settings, which in turn overrides the shared BLT github group (opt-in). Needed while the repository is private, or if GitHub rate-limits the site.
BLT_TOOLKIT_SWITCH_COOKIE, BLT_TOOLKIT_SWITCH_SECURE_COOKIE, BLT_TOOLKIT_SWITCH_OLDUSER_COOKIE Override the switch cookie names (SW-3), as User Switching allows for its own.

Templates

templates/username-form.php renders the self-service form. Copy it to your-theme/blt-toolkit/username-form.php (a child theme's copy wins over the parent's) to change the markup. Keep the field names, the hidden fields and the form's method and action; the template's header lists every value it receives in $args. The server checks the nonce, the capability, the cooldown and every validation rule whatever the markup says.

CSS tokens

BLT Toolkit puts two things on the front end: the "Switch back" link (SW-17) and the username form (UN-23). Both are styled by assets/css/blt-toolkit-front.css with .blt-tk-* classes and --blt-tk-* custom properties: no global resets, no framework, safe inside a Bricks page, and nothing loaded for visitors who are not switched or not on a page with the shortcode. The tk prefix is pending confirmation that it does not collide with BLT Tube's (D14).

The stylesheet only reads the tokens, never declares them, so a theme can set any of them on :root, body or a wrapper. Each falls back to the BLT design token where there is one, then to the literal value. The list is also in the stylesheet's header. The username form uses the first five as well, for its button and focus ring.

Token Used for Fallback
--blt-tk-accent "Switch back" link background --blt-primary, then #2271b1
--blt-tk-accent-hover Background on hover and focus --blt-primary-hover, then #135e96
--blt-tk-on-accent Link text --blt-surface, then #ffffff
--blt-tk-focus Focus ring --blt-fg, then #1e1e1e
--blt-tk-radius Corner radius --blt-radius-sm, then 4px
--blt-tk-font-size Text size 13px
--blt-tk-offset Distance from the bottom-left corner 16px
--blt-tk-z Stacking order 99999
--blt-tk-shadow Shadow 0 2px 8px rgba(0, 0, 0, 0.25)
--blt-tk-form-width Username form: largest width 32rem
--blt-tk-gap Username form: space between rows 1em
--blt-tk-input-bg Username form: field background --blt-surface, then #fff
--blt-tk-input-fg Username form: field text --blt-fg, then #1e1e1e
--blt-tk-input-border Username form: field border #8c8f94
--blt-tk-muted Username form: help text Inherits the text color
--blt-tk-success-bg, --blt-tk-success-fg Username form: success message background, and its text and border --blt-success-bg / --blt-success-fg, then #edfaef / #005c12
--blt-tk-danger-bg, --blt-tk-danger-fg Username form: error message background, and its text and border --blt-danger-bg / --blt-danger-fg, then #fcf0f1 / #8a2424
--blt-tk-warning-bg, --blt-tk-warning-fg Username form: switched-session warning background, and its text and border --blt-warning-bg / --blt-warning-fg, then #fef8ee / #94660c

The toolbar's "Viewing as" node uses var(--blt-primary, #2271b1). Admin screens use the family design system (blt-design-system.css) and its tokens.

Hooks

Every public action and filter, with its arguments, default and requirement, is documented in HOOKS.md: 15 actions, 24 filters, core's shortcode_atts_blt_toolkit_username_form for the form, User Switching's action names (switch_to_user, switch_back_user, switch_off_user), which BLT Toolkit fires with User Switching's signatures while Work with tools made for User Switching (Settings > Switching) is on, and the current_user_switched() shim.

REST API

Namespace blt-toolkit/v1. The routes serve BLT Toolkit's own admin screens; every one declares a permission_callback and answers only with what the current user may act on.

Route Method Purpose
/switch/users?search= GET Toolbar "Switch to…" typeahead. At least 2 characters, at most 20 results (blt_toolkit_switch_search_limit), only users the current user may switch to, each with a nonced switch URL generated on the server (SW-20). An optional role (a role ID) searches only that role's holders, for the Roles screen's Test as… search (X-3).
/usernames/check GET Live, advisory validation of a new username while typing (UN-16). The server validates again when the form is saved.
/jobs/{id} GET A background job's progress: status, done and total, elapsed time, estimate, current item, failed items and warnings (JOB-4).
/jobs/{id}/tick POST Drives a background job while its screen is open (JOB-1).
/jobs/{id}/cancel POST Cancels a job; items that finished stay finished (JOB-4).
/jobs/{id}/retry POST Starts another pass over a finished job's failed items (JOB-4).

The four job routes need the capability of the job's type (BLT_Toolkit_Jobs::current_user_can_use(): manage_options for the log prune; a bulk rename also only for the person who started it or an administrator) and a REST nonce; a job whose module is off answers to nobody.

BLT Toolkit also adds a blt_toolkit_switch_url field to core's user REST object: the switch URL, or null unless the current user may switch to that user (SW-27). Core's own users endpoint is covered by the protection rules: the role a non-administrator may assign is limited (SEC-4), an empty roles list needs promote_user (SEC-5), and administrators are hidden from non-administrators, anonymous visitors included (SEC-6).

Roles and capabilities

Granted to the administrator role on activation, and to nobody else unless the owner does it. No role is created on activation.

Capability Lets the holder Tier
blt_switch_users Switch to users (subject to the protection rules) and switch off Critical
blt_change_usernames Change other users' usernames Critical
blt_change_own_username Change their own username (self-service) Standard
blt_edit_roles Open the Roles screen and edit roles in Simple view Critical
blt_edit_capabilities Use Advanced view, add and delete capabilities, edit per-user capabilities Critical
blt_create_roles Add roles Critical
blt_delete_roles Delete and merge roles Critical
blt_reset_roles Reset to defaults, restore snapshots, import roles Critical
blt_view_toolkit_log View and export the activity log Elevated

Settings, module toggles and legacy imports need manage_options.

Two meta capabilities are checked against a target user and never granted directly:

Meta capability Maps to
blt_switch_to_user blt_switch_users, plus the protection rules. Never allowed for yourself or a user who does not exist.
blt_change_username For yourself: blt_change_own_username plus the cooldown. For others: blt_change_usernames plus the protection rules.

Deliberate difference from User Switching. User Switching lets anyone who can edit a user switch into them. BLT Toolkit requires the explicit blt_switch_users capability, so a role that gains edit_users through some unrelated customization does not silently gain impersonation (D3).

Protection rules (SPEC 5.3), enforced for admin screens, admin-ajax.php, admin-post.php, REST and WP-CLI alike:

  • A non-administrator can never edit, delete, remove, re-role, switch into or rename an administrator (SEC-1).
  • A non-administrator can never edit, delete, remove, re-role, switch into or rename a user more powerful than themselves, never grant a capability they lack, and can only assign roles whose capabilities they hold (SEC-2, SEC-3, SEC-4). "Edit" includes creating application passwords and sending password resets, which core maps to edit_user.
  • A non-administrator can never change a role they hold, or their own roles (SEC-5), and never adds, removes or replaces someone's roles in a way that leaves that person with a capability they lack: taking away a role that denies a capability another role grants gives it back, so it counts as granting it (SEC-2, SEC-3).
  • Nobody moves the people who hold administrator to another role (SEC-12).
  • Granting a Critical capability to any role other than administrator, or to any user, needs a review step and the role's name typed to confirm (SEC-10). Tier badges appear next to every control (SEC-11).
  • The administrator role is read-only in Simple view, cannot be deleted, keeps its ID, and keeps its lockout-critical capabilities (read, manage_options, list_users, edit_users, promote_users, activate_plugins, blt_edit_roles, blt_edit_capabilities) in Advanced view (SEC-12). A save that would leave you without blt_edit_roles is refused unless another administrator exists and you confirm (SEC-13).

The Roles screen (BLT Toolkit > Roles, or Users > Roles; blt_edit_roles):

  • Role list: every role with its user count and badges (Built-in, Default, Has Critical capabilities) and its description. Add role (blt_create_roles) starts from Blank (read only), Subscriber, Contributor, Author or Editor (WordPress's default capabilities), or a copy of a role whose capabilities you all hold yourself (SEC-3); the ID is suggested from the name and cannot be changed later (RO-30). A role with Critical capabilities is confirmed by typing its name (SEC-10).
  • Role header: the name, ID, a user count linking to the filtered Users list, Rename (display name only, RO-31), Move users to a new role (the way to change an ID: a copy under the new ID, everyone moved by a background job, then an offer to delete the old one, RO-32; never for Administrator, whose ID cannot change, SEC-12), Merge into another role and Delete (blt_delete_roles; never administrator, WordPress's own roles, the default role or an additional default role; people who hold a deleted role get the replacement you choose, or simply lose it when they have other roles, RO-40 to RO-42), Compare (two roles side by side by group, read only, RO-25) and Test as… (switch to one of up to 20 holders you may switch to and land on their dashboard, X-3).
  • Simple view (blt_edit_roles; the only view for someone without blt_edit_capabilities): Basics, Content, Media and comments, Appearance, Plugins, Site, People, BLT Toolkit and Other plugin permissions (read only). Content has one row per content type capability group, labeled with every type that shares it ("Posts (also used by: Events)"), with a level: No access, View, Contribute, Author (own only), Edit all or Full control, plus Can delete (Contribute to Edit all) and Manage {taxonomy} for taxonomies with permissions of their own (SPEC 10.3, 10.4). WordPress's default roles read as you would expect: Contributor is Contribute with Can delete on Posts and No access on Pages, Editor is Full control. A role whose capabilities match no level shows Custom, a partly granted multi-capability toggle shows as partly on, and neither is changed unless you pick a state, after a review listing what is removed (RS-4). Saving changes only the controls you changed (RS-5); a Critical grant, a change that removes a Custom row's capabilities, or more than ten capabilities goes through a review first, with the role's name typed for Critical grants (RS-6, SEC-10). Hints such as "People with this role usually also need to upload images" come with Also allow (RS-7, RS-17) and are announced to screen readers when a change shows them. A type that maps its capabilities to site-wide ones (ACF field groups use manage_options) is shown as Controlled by: Change settings; a type registered with map_meta_cap false shows Custom ownership rules and is edited in Advanced (RS-11, RS-12). Edit in Advanced opens the matrix narrowed to that control's capabilities. Administrator is read only (SEC-12). Works without JavaScript.
  • Separate permissions for a custom post type (blt_edit_capabilities and manage_options; RS-30 to RS-35). Most custom post types share Posts' capabilities, so a level on one would change Posts too. Give {Type} separate permissions on its row confirms a singular and plural (event, events), shows a dry run of the *_events capabilities each role and each user with Posts capabilities of their own will receive so that nobody's access changes on day one, then snapshots, writes the roles once and logs it. The type then has its own row and levels, and its View level means read only in the dashboard: the list of items with titles as plain text, no editor, no Add New, and a 403 from the REST API for creating or editing, enforced by BLT Toolkit with the blt_readonly_{plural} marker (RS-13, RS-14; "Enforced by BLT Toolkit" on the row). For Posts, Pages, types that share them, and types whose edit capability is not named edit_{plural}, View means on the site only. Share permissions again reverts a type; a separate dry-run step then offers to remove the unused *_{plural} capabilities, which stay flagged as orphaned until you do. If another plugin overrides an isolated type, or registers it before BLT Toolkit loads, an admin notice and Site Health name it and its level selector is switched off (RS-33). bricks_template is never offered (blt_toolkit_isolation_excluded_types, RS-35). Separate permissions and read-only enforcement keep working while the Roles module is off; turn it back on to change them.
  • Advanced view (blt_edit_capabilities): the capability matrix with a group tree (Core areas, one node per content type capability group with every type that shares it, taxonomies, Plugins (detected), BLT Toolkit, Orphaned). A post type registered with capability_type => 'post' is listed inside Posts, labeled "also used by", never as its own node (RO-14). Each row shows the label, the raw name, its source and its risk tier. The toolbar has a quick filter, Granted only, Show deprecated, Readable names and 1 to 3 columns, all of them links or GET fields so the page works without JavaScript; with JavaScript the filter is instant and the save bar counts changes. A save changes only the capabilities that were on the page, is refused with the difference if someone else changed the role since you opened it, takes a snapshot, and writes the roles option once (RO-1 to RO-3). More than ten changes, a Critical grant or a change that would lock you out go through a review page first.
  • Add capability (administrators only, since a new capability is one nobody else holds: lowercase letters, digits, _ and -, at most 191 characters, never a meta capability name such as edit_post) to the roles you choose (RO-23). Delete capability is offered only for orphaned capabilities, those nothing on the site registers, with every role and user holding it listed and a dry run first (RO-24).
  • Role details: a description shown in the role list, and Hide the toolbar on the site, applied only when every one of a user's roles has it (capabilities are additive). A switched user whose toolbar is hidden still gets the "Switch back" link at the foot of the page (SPEC 9.5, X-5).

Users and profiles (Roles module):

  • Additional roles on another user's profile and on Add New User, below WordPress's Role dropdown, which stays the main role. Only roles you could give are offered (SEC-4), never on your own profile (SEC-5) (US-1).
  • Add role… and Remove role… with an Apply button next to "Change role to…" on the Users list, checked per user and logged as one row (US-3).
  • Users without a role (no roles at all, or only roles that no longer exist, as after a full reset or a restore that deletes roles) are counted only when you press Count on the Users list or the Overview, cached for an hour, and listed by a "No role (N)" view; the count is never run on a normal page load (US-12).
  • Capabilities on the profile replaces WordPress's "Additional Capabilities" row: it lists the user's own grants and denies, and Edit capabilities (blt_edit_capabilities, blt_edit_roles and permission to change the user's role) opens the Roles screen for that user, where each capability is Inherit, Grant or Deny with where it comes from. A save writes the user's capabilities once, keeps their roles, and asks for the username before granting a Critical capability (US-10, US-11, SEC-10).

Optional role. Support Agent (blt_support_agent: read, list_users, blt_switch_users) is created only when the owner clicks Create Support Agent role… under Settings > Switching, checks the preview and types the role's name to confirm. The protection rules keep it from switching into anyone more powerful than itself (SW-21): on a default site it can switch into Subscribers, never into Contributors, Authors, Editors or Administrators. The deprecated level_0 to level_10 capabilities are left out of that comparison (SEC-8).

Development

composer install      # dev tools only: phpcs, WPCS, PHPCompatibility, PHPUnit
composer lint         # php -l on every file
composer phpcs        # WordPress-Extra, PHPCompatibilityWP 7.4+
composer test         # PHPUnit, no WordPress install required
node bin/make-pot.js  # regenerate languages/blt-toolkit.pot (CI fails on drift)
bin/dev-site.sh DIR   # disposable WordPress on SQLite for smoke tests (see the script header)

In a container that runs as root, prefix each Composer command with COMPOSER_ALLOW_SUPERUSER=1 (see CLAUDE.md).

Pure logic (the superset rule, role diffs and hashes, the Simple view mapper, author slugs, username rules, bulk rename planning, CSV parsing, the switch cookie stack, the privacy eraser's matching and scrubbing, and the Site Health results) lives in classes and methods that take plain arrays and strings, and is tested against known vectors. WordPress-coupled behavior is exercised against a real WordPress install; the acceptance criteria and the manual smoke matrix are in tasks/todo.md.

CI (.github/workflows/ci.yml) runs on every push and pull request, main included: the syntax check on PHP 7.4 to 8.3, phpcs, the unit tests on PHP 7.4, 8.2 and 8.3, the version-sync check, the POT drift check, and a release-zip build and verification. A merge to main publishes the release (.github/workflows/release.yml, ported from BLT Secure): lint, phpcs, the unit tests and the POT drift check run again on that commit, and only if they pass is the version bumped (patch by default, #minor or #major in the merge commit message) in the plugin header, BLT_TOOLKIT_VERSION and the readme.txt stable tag, tagged vX.Y.Z, and blt-toolkit-X.Y.Z.zip is built with one top-level blt-toolkit/ folder and the update checker inside. The repository stays at 0.1.0 until then; the merge that completes Phase 7 carries #major to publish 1.0.0.

Requirements and phases are in SPEC.md, reposi

This README is longer than the copy stored here. Read the rest on GitHub →