Integrate the Users plugin into a NocoBase App, build a user management page on its API and configure its permissions, or add an application-owned role scope through the public Server contracts.
npx skills add https://github.com/nocobase/nocobase --skill nocobase-app-plugin-users
Use this Skill when an application needs user management, or when another
plugin needs to expose application-specific roles in user management. Do
not use it to modify the Users plugin source or to replace Authentication's
user, account, or Session storage.
UsersClientOptions:@nocobase/app-plugin-users/client. It contributes only the public
invitation page at /invite/:token; there is no user management page.
UsersClient and its types from@nocobase/app-plugin-users/client/user-client.
userManagementServiceToken,userRoleScopeRegistryToken, UserRoleScope, and related types from
@nocobase/app-plugin-users/server/tokens.
GET /api/users (page, pageSize, q, status, roleScope, role, sort=name, order=asc|desc; answers { data, meta: { page, pageSize, total } }), GET /api/users/options, GET /api/users/:userId (latest managed profile and role scopes, requires user/read, 404 for absent accounts), POST /api/users, PATCH /api/users/:userId, DELETE /api/users/:userId?confirm=true, POST /api/users/:userId/disable|enable|resetPassword|revokeSessions, and PUT /api/users/:userId/roleScopes/:scope. The running application documents each one, with request and response schemas, at /api/swagger/docs (JSON at /api/swagger) under the Users tag. Failures use the standard error body; branch on error.reason, such as USER_NOT_FOUND, SELF_DELETE_NOT_ALLOWED, LAST_ASSIGNMENT or USER_EMAIL_CONFLICT.UsersClient.update(userId, { name?, username?, email?, roleScopes? }) supports one atomic editor Save. Profile fields require update; submitted roleScopes additionally require assign-role. A roles-only payload requires assign-role without update. The service validates scope values, runs each applicable target-account guard, and commits profile changes and all submitted scope replacements in the caller-owned transaction, notifying permission clients only after commit. Omitted scopes are preserved. Keep the existing per-scope PUT for callers needing only one scope. Do not implement a combined Save as sequential profile and role requests: a later failure would leave a partially saved account.
Every HTTP route requires Authentication and Authorization. Routes check the user record type with the applicable action from read, create, update, disable, enable, assign-role, reset-password, revoke-sessions or delete. Account creation checks both create and assign-role. delete is permitted only while an application role scope can clean a deleted user up.
and Server plugin arrays. The Client factory takes no placement options:
users().
user actions those roles need, on { type: 'user', id: '*' } because user is a record type. The plugin creates no roles and grants no access by itself.UsersClient and declare it among the App's own routes with the page access it needs; the plugin ships none.The invitation page reports results through useToaster() from @nocobase/app-client, so the App needs the @nocobase/app-client that exports it and registers a toaster service, as the templates do: client/lib/toaster.ts from the template, and this.app.container.instance(toasterToken, createToaster()) in the register() of client/service-provider.ts, with the Toaster component mounted in client/react-providers.ts. Without the registration nothing throws, but its toasts are only logged to the browser console.
The default app permission-set scope is supplied by Users whenever the Authorization plugin's authorizationToken is available. Do not copy a user-roles Provider into an application. Set users.permissionSets: false to replace the default with an application-owned scope.
Resolve userRoleScopeRegistryToken in an application or business plugin
ServiceProvider and register one UserRoleScope during boot(). The scope owns
its available options, current assignment lookup, role filtering, atomic
replacement, and any disable guard. Unregister it during shutdown().
Use the DatabaseConnection passed to each scope method. This is the
caller-owned transaction used to keep account creation or state changes atomic
with role assignments. Do not open an unrelated transaction and do not write
Authentication's internal tables.
Guard who may act on whom with assertCanAdminister(userId, actorId, action, connection): the management API calls it for update, disable, enable, reset-password, revoke-sessions and assign-role with the authenticated actor, in the action's transaction and before anything changes, after authorization of the action itself has passed. Throw a UserRoleScopeError to refuse, for example when someone who is not an application owner resets an owner's password; the route answers FAILED_PRECONDITION with the scope's reason. Service callers that pass no actorId are the application's own server-side work and are not asked. Deletion has its own assertCanDelete(userId, actorId, connection).
For a required single-role scope, set selection: 'single' and
requiredOnCreate: true. Reject invalid values in the scope and enforce
business invariants such as the last-administrator rule on the server.
Set assignable: false or removable: false on an option when user management
must show a protected assignment but must not add or revoke it. Enforce the
same rule in replace() because these flags only control the Client. Set
hasAuthenticatedDefaultAccess: true when the scope lists direct assignments
but all signed-in users also inherit separately configured default access, so a
user management page can explain that distinction instead of treating the
default as a role.
Use labelI18nKey with labelI18nNs on a scope or option when its owner has
registered Client locale resources; keep label as the readable fallback.
Implement optional getMany() when assignments can be read as a batch. Users
uses it for list pages and falls back to get() for existing scopes.
Holders of invite on user can create invitations through POST /api/users/invitations or UsersClient; choosing roles also requires assign-role. Results include a shareable link even when email succeeds. A queued invitation closed or rotated by another request returns emailSent=false without a link, while the other recipients still complete. Original inviters can renew with sendEmail=false, which invalidates the old link. Plugin-specific invitations must use their domain endpoint, which rechecks its own grants. Other managers may resend email but receive no link. Tokens last seven days; only hashes are stored.
New accounts register directly from the invitation link with a name and password; acceptance leaves emailVerified=false. Email delivery is optional: authorized original inviters can privately forward the link when the email channel is absent or fails. Links use app.publicOrigin when configured, otherwise the invitation request origin. Existing accounts must sign in with the invited email. Acceptance consumes only the supplied invitation and applies only its grants; handlers run in its transaction and roll back account creation on failure.
hashing, and Sessions.
user authorization handler, and role-scope registry.
the user management page, and role-specific invariants.
skills/ source is authoritative. .agents/skills/ is asynchronized copy and must not be edited.
authenticates and authorizes every request.
grant; use explicit static grants for this resource.
Sessions and Realtime connections.
never returned by the service or included in security events.
401; authenticated requests without therequested user action return 403.
assignment failure rolls back the user.
409 conflict. Password resetand database Session revocation commit or roll back together.
the user requires a new login.
assignments outside the registered scope.
synchronization alone proves only that the copy matches this source.
Deletion uses DELETE /api/users/:userId?confirm=true and user/delete authorization. Obtain an explicit user deletion request before calling it. Application role scopes can guard deletion and clean dependent credentials transactionally. Historical user identities are retained but cannot sign in or appear in management lists.
Invitation emails use notificationService.sendTransient() so credentials never enter notification message snapshots or job storage. This performs one bounded attempt without durable retries or deduplication. Failures retain safe status/category diagnostics without raw provider messages. The inviter can generate and privately deliver a new link if delivery fails. Historical notification snapshots are not rewritten.
Apply an option scope’s defaultValue only when creating a new account. An explicit empty array takes precedence; editing uses saved roles and never reapplies defaults. Member is a visible direct role that can be removed. A roleless person retains authenticated account basics but no role-granted business access. Upgrade Authentication, Authorization, and Users together, stop old writers before applying database tasks, and merge the template user pages and translations separately: upgrading plugins does not replace application-owned source. The new Authorization seed preserves existing users before removing Member’s global assignment; never recreate that assignment in application seeds or startup reconciliation.
Take nocobase/nocobase-app-plugin-users from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.