From dc028a5b8b171e24bd51c5765a4673a4ae6811ca Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 31 Mar 2026 17:37:02 +0200
Subject: [PATCH 01/26] Update Features > Users & Permissions to include /users
endpoints
---
.../docs/cms/features/users-permissions.md | 215 +++++++++++++++++-
1 file changed, 213 insertions(+), 2 deletions(-)
diff --git a/docusaurus/docs/cms/features/users-permissions.md b/docusaurus/docs/cms/features/users-permissions.md
index 2190771e60..50baf404a5 100644
--- a/docusaurus/docs/cms/features/users-permissions.md
+++ b/docusaurus/docs/cms/features/users-permissions.md
@@ -565,7 +565,7 @@ Each time an API request is sent the server checks if an `Authorization` header
When you create a user without a role, or if you use the `/api/auth/local/register` route, the `authenticated` role is given to the user.
:::
-#### Basic authentication endpoints
+#### Authentication endpoints {#authentication-endpoints}
The Users & Permissions feature provides the following authentication endpoints for user management and [Content API](/cms/api/rest) access:
@@ -576,8 +576,10 @@ The Users & Permissions feature provides the following authentication endpoints
| `POST` | `/api/auth/forgot-password` | Request password reset |
| `POST` | `/api/auth/reset-password` | Reset password using token |
| `GET` | `/api/auth/email-confirmation` | Confirm user email address |
+| `POST` | `/api/auth/send-email-confirmation` | Resend confirmation email |
+| `POST` | `/api/auth/change-password` | Change password (requires authentication) |
-#### Session management endpoints
+##### Session management endpoints
When [session management](#jwt-management-modes) is enabled (`jwtManagement: 'refresh'`), additional endpoints are available:
@@ -586,6 +588,209 @@ When [session management](#jwt-management-modes) is enabled (`jwtManagement: 're
| `POST` | `/api/auth/refresh` | Refresh access token using refresh token |
| `POST` | `/api/auth/logout` | Revoke user sessions (supports device-specific logout) |
+#### User CRUD endpoints {#user-crud-endpoints}
+
+The Users & Permissions plugin also exposes a set of endpoints for managing user records directly. These endpoints are separate from the authentication endpoints and allow you to create, read, update, and delete user entries:
+
+| Method | URL | Description |
+| ------ | --- | ----------- |
+| `GET` | `/api/users` | Find all users |
+| `GET` | `/api/users/me` | Get the currently authenticated user |
+| `GET` | `/api/users/:id` | Find a specific user by ID |
+| `GET` | `/api/users/count` | Get the total number of users |
+| `POST` | `/api/users` | Create a new user |
+| `PUT` | `/api/users/:id` | Update a user by ID |
+| `DELETE` | `/api/users/:id` | Delete a user by ID |
+
+:::note
+These endpoints are protected by the role-based permission system. To access them, enable the corresponding action (e.g., `find`, `findOne`, `create`, `update`, `delete`) for the desired role in *Users & Permissions plugin > Roles*.
+:::
+
+##### Get the authenticated user
+
+The `GET /api/users/me` endpoint returns the user associated with the current JWT. This is useful for front-end applications that need to display user profile information after login.
+
+
+
+
+```bash
+curl -X GET http://localhost:1337/api/users/me \
+ -H "Authorization: Bearer your-access-token"
+```
+
+
+
+
+
+```json
+{
+ "id": 1,
+ "documentId": "abc123",
+ "username": "kai",
+ "email": "kai@strapi.io",
+ "provider": "local",
+ "confirmed": true,
+ "blocked": false,
+ "createdAt": "2024-01-15T09:00:00.000Z",
+ "updatedAt": "2024-01-15T09:00:00.000Z"
+}
+```
+
+
+
+
+##### Find all users
+
+The `GET /api/users` endpoint returns a list of all users. Populate and filter parameters can be passed as query strings.
+
+
+
+
+```bash
+curl -X GET "http://localhost:1337/api/users?populate=role" \
+ -H "Authorization: Bearer your-access-token"
+```
+
+
+
+
+
+```json
+[
+ {
+ "id": 1,
+ "documentId": "abc123",
+ "username": "kai",
+ "email": "kai@strapi.io",
+ "provider": "local",
+ "confirmed": true,
+ "blocked": false,
+ "role": {
+ "id": 1,
+ "name": "Authenticated",
+ "description": "Default role given to authenticated user.",
+ "type": "authenticated"
+ },
+ "createdAt": "2024-01-15T09:00:00.000Z",
+ "updatedAt": "2024-01-15T09:00:00.000Z"
+ }
+]
+```
+
+
+
+
+##### Create a user
+
+The `POST /api/users` endpoint creates a new user. Unlike `/api/auth/local/register`, this endpoint requires the caller to have the `create` permission for the Users & Permissions plugin.
+
+
+
+
+```bash
+curl -X POST http://localhost:1337/api/users \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer your-access-token" \
+ -d '{
+ "username": "newuser",
+ "email": "newuser@strapi.io",
+ "password": "Password123",
+ "role": 1,
+ "confirmed": true
+ }'
+```
+
+
+
+
+
+```json
+{
+ "id": 2,
+ "documentId": "def456",
+ "username": "newuser",
+ "email": "newuser@strapi.io",
+ "provider": "local",
+ "confirmed": true,
+ "blocked": false,
+ "createdAt": "2024-01-16T10:00:00.000Z",
+ "updatedAt": "2024-01-16T10:00:00.000Z"
+}
+```
+
+
+
+
+##### Update a user
+
+The `PUT /api/users/:id` endpoint updates an existing user by ID.
+
+
+
+
+```bash
+curl -X PUT http://localhost:1337/api/users/2 \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer your-access-token" \
+ -d '{
+ "username": "updateduser"
+ }'
+```
+
+
+
+
+
+```json
+{
+ "id": 2,
+ "documentId": "def456",
+ "username": "updateduser",
+ "email": "newuser@strapi.io",
+ "provider": "local",
+ "confirmed": true,
+ "blocked": false,
+ "createdAt": "2024-01-16T10:00:00.000Z",
+ "updatedAt": "2024-01-17T11:00:00.000Z"
+}
+```
+
+
+
+
+##### Delete a user
+
+The `DELETE /api/users/:id` endpoint deletes a user by ID.
+
+
+
+
+```bash
+curl -X DELETE http://localhost:1337/api/users/2 \
+ -H "Authorization: Bearer your-access-token"
+```
+
+
+
+
+
+```json
+{
+ "id": 2,
+ "documentId": "def456",
+ "username": "updateduser",
+ "email": "newuser@strapi.io",
+ "provider": "local",
+ "confirmed": true,
+ "blocked": false,
+ "createdAt": "2024-01-16T10:00:00.000Z",
+ "updatedAt": "2024-01-17T11:00:00.000Z"
+}
+```
+
+
+
+
To refresh your authentication token you could for instance send the following request:
@@ -773,3 +978,9 @@ create: async ctx => {
ctx.created(data);
};
```
+
+## Customizing routes and policies {#customizing-routes-and-policies}
+
+The Users & Permissions plugin routes and controllers can be extended and overridden through the [plugin extension system](/cms/plugins-development/plugins-extension). This is useful for adding custom policies to user endpoints, overriding controller logic, or adding new routes.
+
+For a complete guide with step-by-step instructions and code examples, see [Customizing Users & Permissions plugin routes](/cms/backend-customization/guides/customizing-users-permissions-plugin-routes).
From 7ba3297fa2b457498e483a60ed1f317135b9c3e7 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 31 Mar 2026 17:37:14 +0200
Subject: [PATCH 02/26] Add backend customization guide for U&P plugins routes
---
...omizing-users-permissions-plugin-routes.md | 597 ++++++++++++++++++
1 file changed, 597 insertions(+)
create mode 100644 docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
new file mode 100644
index 0000000000..42bb361dee
--- /dev/null
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -0,0 +1,597 @@
+---
+title: Customizing Users & Permissions plugin routes
+description: Extend or override routes, controllers, and policies for the Users & Permissions plugin to add custom access control logic.
+displayed_sidebar: cmsSidebar
+tags:
+ - users & permissions
+ - routes
+ - policies
+ - controllers
+ - backend customization
+ - guides
+---
+
+# Customizing Users & Permissions plugin routes
+
+
+The Users & Permissions plugin exposes `/users` and `/auth` routes that can be extended or overridden using the plugin extension system. This guide shows how to add custom policies, override controllers, and add new routes to the User collection.
+
+
+The [Users & Permissions plugin](/cms/features/users-permissions) ships with built-in routes for authentication (`/auth`) and user management (`/users`). Because these routes belong to a plugin rather than a user-created content-type, they cannot be customized with `createCoreRouter`. Instead, you extend them through the [plugin extension system](/cms/plugins-development/plugins-extension) using a `strapi-server.js` or `strapi-server.ts` file in the `./src/extensions/users-permissions/` folder.
+
+:::prerequisites
+- A Strapi 5 project with the Users & Permissions plugin installed (included by default).
+- Familiarity with [routes](/cms/backend-customization/routes) and [policies](/cms/backend-customization/policies).
+:::
+
+## How Users & Permissions routes work {#how-routes-work}
+
+Unlike content-types you create (e.g., `api::restaurant.restaurant`), the Users & Permissions plugin registers its routes inside the `plugin.routes['content-api'].routes` array. This array contains all `/users`, `/auth`, and `/roles` route definitions.
+
+Each route is an object with the following shape:
+
+```js
+{
+ method: 'GET', // HTTP method
+ path: '/users', // URL path (relative to /api)
+ handler: 'user.find', // controller.action
+ config: {
+ prefix: '', // path prefix (empty means /api)
+ policies: [], // array of policies to run before the handler
+ middlewares: [], // array of middlewares
+ },
+}
+```
+
+The available `user` controller actions are: `find`, `findOne`, `create`, `update`, `destroy`, `me`, and `count`.
+
+The available `auth` controller actions are: `callback` (login), `register`, `forgotPassword`, `resetPassword`, `changePassword`, `emailConfirmation`, `sendEmailConfirmation`, `connect`, and `refresh`.
+
+## Extend routes with strapi-server {#extend-routes}
+
+All customizations to the Users & Permissions plugin go in a single file:
+
+
+
+
+
+```js title="./src/extensions/users-permissions/strapi-server.js"
+module.exports = (plugin) => {
+ // Your customizations here
+
+ return plugin;
+};
+```
+
+
+
+
+
+```ts title="./src/extensions/users-permissions/strapi-server.ts"
+export default (plugin) => {
+ // Your customizations here
+
+ return plugin;
+};
+```
+
+
+
+
+
+The function receives the full plugin object and must return it. You can modify `plugin.routes`, `plugin.controllers`, `plugin.policies`, and `plugin.services` before returning.
+
+## Add a custom policy to a user route {#add-custom-policy}
+
+A common requirement is to restrict who can update or delete user accounts. For example, you might want to ensure that users can only update their own profile.
+
+### 1. Create the policy file
+
+Create a global policy that checks whether the authenticated user matches the target user:
+
+
+
+
+
+```js title="./src/policies/is-own-user.js"
+module.exports = (policyContext, config, { strapi }) => {
+ const currentUser = policyContext.state.user;
+
+ if (!currentUser) {
+ return false;
+ }
+
+ const targetUserId = Number(policyContext.params.id);
+
+ if (currentUser.id !== targetUserId) {
+ return false;
+ }
+
+ return true;
+};
+```
+
+
+
+
+
+```ts title="./src/policies/is-own-user.ts"
+export default (policyContext, config, { strapi }) => {
+ const currentUser = policyContext.state.user;
+
+ if (!currentUser) {
+ return false;
+ }
+
+ const targetUserId = Number(policyContext.params.id);
+
+ if (currentUser.id !== targetUserId) {
+ return false;
+ }
+
+ return true;
+};
+```
+
+
+
+
+
+### 2. Attach the policy to the user routes
+
+In the plugin extension file, find the `update` and `delete` routes and add the policy:
+
+
+
+
+
+```js title="./src/extensions/users-permissions/strapi-server.js"
+module.exports = (plugin) => {
+ // Find the routes that need the policy
+ const routes = plugin.routes['content-api'].routes;
+
+ // Add the 'is-own-user' policy to the update route
+ const updateRoute = routes.find(
+ (route) => route.handler === 'user.update'
+ );
+
+ if (updateRoute) {
+ updateRoute.config = updateRoute.config || {};
+ updateRoute.config.policies = updateRoute.config.policies || [];
+ updateRoute.config.policies.push('global::is-own-user');
+ }
+
+ // Add the same policy to the delete route
+ const deleteRoute = routes.find(
+ (route) => route.handler === 'user.destroy'
+ );
+
+ if (deleteRoute) {
+ deleteRoute.config = deleteRoute.config || {};
+ deleteRoute.config.policies = deleteRoute.config.policies || [];
+ deleteRoute.config.policies.push('global::is-own-user');
+ }
+
+ return plugin;
+};
+```
+
+
+
+
+
+```ts title="./src/extensions/users-permissions/strapi-server.ts"
+export default (plugin) => {
+ // Find the routes that need the policy
+ const routes = plugin.routes['content-api'].routes;
+
+ // Add the 'is-own-user' policy to the update route
+ const updateRoute = routes.find(
+ (route) => route.handler === 'user.update'
+ );
+
+ if (updateRoute) {
+ updateRoute.config = updateRoute.config || {};
+ updateRoute.config.policies = updateRoute.config.policies || [];
+ updateRoute.config.policies.push('global::is-own-user');
+ }
+
+ // Add the same policy to the delete route
+ const deleteRoute = routes.find(
+ (route) => route.handler === 'user.destroy'
+ );
+
+ if (deleteRoute) {
+ deleteRoute.config = deleteRoute.config || {};
+ deleteRoute.config.policies = deleteRoute.config.policies || [];
+ deleteRoute.config.policies.push('global::is-own-user');
+ }
+
+ return plugin;
+};
+```
+
+
+
+
+
+With this configuration, `PUT /api/users/:id` and `DELETE /api/users/:id` will return a `403 Forbidden` error if the authenticated user does not match the `:id` in the URL.
+
+:::tip
+For a more informative error message, throw a `PolicyError` instead of returning `false`:
+
+```js
+const { errors } = require('@strapi/utils');
+const { PolicyError } = errors;
+
+// Inside the policy:
+throw new PolicyError('You can only modify your own account');
+```
+
+See the [policies documentation](/cms/backend-customization/policies) for more details.
+:::
+
+## Override a controller action {#override-controller}
+
+You can replace or wrap an existing controller action. For instance, to add custom logic to the `me` endpoint:
+
+
+
+
+
+```js title="./src/extensions/users-permissions/strapi-server.js"
+module.exports = (plugin) => {
+ const originalMe = plugin.controllers.user.me;
+
+ plugin.controllers.user.me = async (ctx) => {
+ // Call the original controller
+ await originalMe(ctx);
+
+ // Add extra data to the response
+ if (ctx.body) {
+ ctx.body.timestamp = new Date().toISOString();
+ }
+ };
+
+ return plugin;
+};
+```
+
+
+
+
+
+```ts title="./src/extensions/users-permissions/strapi-server.ts"
+export default (plugin) => {
+ const originalMe = plugin.controllers.user.me;
+
+ plugin.controllers.user.me = async (ctx) => {
+ // Call the original controller
+ await originalMe(ctx);
+
+ // Add extra data to the response
+ if (ctx.body) {
+ ctx.body.timestamp = new Date().toISOString();
+ }
+ };
+
+ return plugin;
+};
+```
+
+
+
+
+
+:::caution
+When wrapping a controller, always call the original function first to preserve the default behavior. Skipping the original function means you take over the full request handling, including sanitization and error handling.
+:::
+
+## Add a new route {#add-new-route}
+
+You can add custom routes to the Users & Permissions plugin. For example, to add an endpoint that deactivates a user account:
+
+
+
+
+
+```js title="./src/extensions/users-permissions/strapi-server.js"
+module.exports = (plugin) => {
+ // Add a new controller action
+ plugin.controllers.user.deactivate = async (ctx) => {
+ const { id } = ctx.params;
+
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .edit(id, { blocked: true });
+
+ ctx.body = { message: `User ${user.username} has been deactivated` };
+ };
+
+ // Register the route
+ plugin.routes['content-api'].routes.push({
+ method: 'POST',
+ path: '/users/:id/deactivate',
+ handler: 'user.deactivate',
+ config: {
+ prefix: '',
+ policies: ['global::is-own-user'],
+ },
+ });
+
+ return plugin;
+};
+```
+
+
+
+
+
+```ts title="./src/extensions/users-permissions/strapi-server.ts"
+export default (plugin) => {
+ // Add a new controller action
+ plugin.controllers.user.deactivate = async (ctx) => {
+ const { id } = ctx.params;
+
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .edit(id, { blocked: true });
+
+ ctx.body = { message: `User ${user.username} has been deactivated` };
+ };
+
+ // Register the route
+ plugin.routes['content-api'].routes.push({
+ method: 'POST',
+ path: '/users/:id/deactivate',
+ handler: 'user.deactivate',
+ config: {
+ prefix: '',
+ policies: ['global::is-own-user'],
+ },
+ });
+
+ return plugin;
+};
+```
+
+
+
+
+
+After restarting Strapi, `POST /api/users/:id/deactivate` becomes available. Grant the corresponding permission in the admin panel under *Users & Permissions plugin > Roles* for the roles that should access this endpoint.
+
+## Override an auth route {#override-auth-route}
+
+Authentication routes can be customized in the same way. For example, to add a rate-limiting middleware to the login endpoint or to run custom logic after registration:
+
+
+
+
+
+```js title="./src/extensions/users-permissions/strapi-server.js"
+module.exports = (plugin) => {
+ const originalRegister = plugin.controllers.auth.register;
+
+ plugin.controllers.auth.register = async (ctx) => {
+ // Call the original register logic
+ await originalRegister(ctx);
+
+ // Custom post-registration logic
+ if (ctx.body && ctx.body.user) {
+ strapi.log.info(`New user registered: ${ctx.body.user.email}`);
+ }
+ };
+
+ return plugin;
+};
+```
+
+
+
+
+
+```ts title="./src/extensions/users-permissions/strapi-server.ts"
+export default (plugin) => {
+ const originalRegister = plugin.controllers.auth.register;
+
+ plugin.controllers.auth.register = async (ctx) => {
+ // Call the original register logic
+ await originalRegister(ctx);
+
+ // Custom post-registration logic
+ if (ctx.body && ctx.body.user) {
+ strapi.log.info(`New user registered: ${ctx.body.user.email}`);
+ }
+ };
+
+ return plugin;
+};
+```
+
+
+
+
+
+## Remove a route {#remove-route}
+
+You can disable a route by filtering it out of the routes array. For example, to disable the user count endpoint:
+
+
+
+
+
+```js title="./src/extensions/users-permissions/strapi-server.js"
+module.exports = (plugin) => {
+ plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter(
+ (route) => route.handler !== 'user.count'
+ );
+
+ return plugin;
+};
+```
+
+
+
+
+
+```ts title="./src/extensions/users-permissions/strapi-server.ts"
+export default (plugin) => {
+ plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter(
+ (route) => route.handler !== 'user.count'
+ );
+
+ return plugin;
+};
+```
+
+
+
+
+
+## Combine multiple customizations {#combine-customizations}
+
+In practice, you often combine several customizations in the same file. The following example adds a policy to `update` and `delete`, wraps the `me` controller, and adds a new route:
+
+
+
+
+
+```js title="./src/extensions/users-permissions/strapi-server.js"
+module.exports = (plugin) => {
+ const routes = plugin.routes['content-api'].routes;
+
+ // 1. Add 'is-own-user' policy to update and delete
+ for (const route of routes) {
+ if (route.handler === 'user.update' || route.handler === 'user.destroy') {
+ route.config = route.config || {};
+ route.config.policies = route.config.policies || [];
+ route.config.policies.push('global::is-own-user');
+ }
+ }
+
+ // 2. Wrap the 'me' controller to include the user's role
+ const originalMe = plugin.controllers.user.me;
+
+ plugin.controllers.user.me = async (ctx) => {
+ await originalMe(ctx);
+
+ if (ctx.state.user && ctx.body) {
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .fetch(ctx.state.user.id, { populate: ['role'] });
+
+ ctx.body.role = user.role;
+ }
+ };
+
+ // 3. Add a custom route
+ plugin.controllers.user.profile = async (ctx) => {
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .fetch(ctx.state.user.id, { populate: ['role'] });
+
+ ctx.body = {
+ username: user.username,
+ email: user.email,
+ role: user.role?.name,
+ createdAt: user.createdAt,
+ };
+ };
+
+ routes.push({
+ method: 'GET',
+ path: '/users/profile',
+ handler: 'user.profile',
+ config: { prefix: '' },
+ });
+
+ return plugin;
+};
+```
+
+
+
+
+
+```ts title="./src/extensions/users-permissions/strapi-server.ts"
+export default (plugin) => {
+ const routes = plugin.routes['content-api'].routes;
+
+ // 1. Add 'is-own-user' policy to update and delete
+ for (const route of routes) {
+ if (route.handler === 'user.update' || route.handler === 'user.destroy') {
+ route.config = route.config || {};
+ route.config.policies = route.config.policies || [];
+ route.config.policies.push('global::is-own-user');
+ }
+ }
+
+ // 2. Wrap the 'me' controller to include the user's role
+ const originalMe = plugin.controllers.user.me;
+
+ plugin.controllers.user.me = async (ctx) => {
+ await originalMe(ctx);
+
+ if (ctx.state.user && ctx.body) {
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .fetch(ctx.state.user.id, { populate: ['role'] });
+
+ ctx.body.role = user.role;
+ }
+ };
+
+ // 3. Add a custom route
+ plugin.controllers.user.profile = async (ctx) => {
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .fetch(ctx.state.user.id, { populate: ['role'] });
+
+ ctx.body = {
+ username: user.username,
+ email: user.email,
+ role: user.role?.name,
+ createdAt: user.createdAt,
+ };
+ };
+
+ routes.push({
+ method: 'GET',
+ path: '/users/profile',
+ handler: 'user.profile',
+ config: { prefix: '' },
+ });
+
+ return plugin;
+};
+```
+
+
+
+
+
+## Validation
+
+After making changes, restart Strapi and verify your customizations:
+
+1. Run `yarn strapi routes:list` to confirm your new or modified routes appear.
+2. Test protected routes without authentication to verify policies return `403 Forbidden`.
+3. Test with an authenticated user to confirm the expected behavior.
+4. Check the Strapi server logs for errors during startup.
+
+## Troubleshooting
+
+| Symptom | Possible cause |
+| ------- | -------------- |
+| Route not found (404) | The route was not pushed to `plugin.routes['content-api'].routes`, or the `prefix` property is missing. |
+| Policy not applied | The policy name is incorrect. Global policies require the `global::` prefix (e.g., `global::is-own-user`). |
+| Controller returns 500 | The controller action name does not match the `handler` value in the route definition. |
+| Changes not reflected | Strapi was not restarted after modifying the extension file. Extensions are loaded at startup. |
+| Permission denied (403) | The new action is not enabled for the role. Enable it in *Users & Permissions plugin > Roles*. |
From 17fa8f3af4374abe45095b953c2c3c6afec435d8 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 31 Mar 2026 18:19:24 +0200
Subject: [PATCH 03/26] Remove useless custom anchor id
---
docusaurus/docs/cms/features/users-permissions.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docusaurus/docs/cms/features/users-permissions.md b/docusaurus/docs/cms/features/users-permissions.md
index 50baf404a5..0007abd6fb 100644
--- a/docusaurus/docs/cms/features/users-permissions.md
+++ b/docusaurus/docs/cms/features/users-permissions.md
@@ -588,7 +588,7 @@ When [session management](#jwt-management-modes) is enabled (`jwtManagement: 're
| `POST` | `/api/auth/refresh` | Refresh access token using refresh token |
| `POST` | `/api/auth/logout` | Revoke user sessions (supports device-specific logout) |
-#### User CRUD endpoints {#user-crud-endpoints}
+#### User CRUD endpoints
The Users & Permissions plugin also exposes a set of endpoints for managing user records directly. These endpoints are separate from the authentication endpoints and allow you to create, read, update, and delete user entries:
From 208bd4c6ccb8e5d0b0747bd9b8128a560cc8dd58 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 31 Mar 2026 18:19:32 +0200
Subject: [PATCH 04/26] =?UTF-8?q?Fix=20naming:=20plugin=20=E2=86=92=20feat?=
=?UTF-8?q?ure?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docusaurus/docs/cms/features/users-permissions.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docusaurus/docs/cms/features/users-permissions.md b/docusaurus/docs/cms/features/users-permissions.md
index 0007abd6fb..fd09eefe4b 100644
--- a/docusaurus/docs/cms/features/users-permissions.md
+++ b/docusaurus/docs/cms/features/users-permissions.md
@@ -590,7 +590,7 @@ When [session management](#jwt-management-modes) is enabled (`jwtManagement: 're
#### User CRUD endpoints
-The Users & Permissions plugin also exposes a set of endpoints for managing user records directly. These endpoints are separate from the authentication endpoints and allow you to create, read, update, and delete user entries:
+The Users & Permissions feature also exposes a set of endpoints for managing user records directly. These endpoints are separate from the authentication endpoints and allow you to create, read, update, and delete user entries:
| Method | URL | Description |
| ------ | --- | ----------- |
From ddeab203b5513460a95360a394c6844b10138844 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Mon, 13 Apr 2026 16:20:34 +0200
Subject: [PATCH 05/26] Update docs with Claude Opus 4.6 after another
integrity checker run
Co-Authored-By: Claude Opus 4.6
---
...omizing-users-permissions-plugin-routes.md | 206 +++--
.../docs/cms/features/users-permissions.md | 98 +--
docusaurus/sidebars.js | 9 +
docusaurus/static/llms-code.txt | 710 +++++++++++++++++-
docusaurus/static/llms-full.txt | 194 +++++
docusaurus/static/llms.txt | 1 +
6 files changed, 1094 insertions(+), 124 deletions(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index 42bb361dee..fcb83a1d2f 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -17,7 +17,7 @@ tags:
The Users & Permissions plugin exposes `/users` and `/auth` routes that can be extended or overridden using the plugin extension system. This guide shows how to add custom policies, override controllers, and add new routes to the User collection.
-The [Users & Permissions plugin](/cms/features/users-permissions) ships with built-in routes for authentication (`/auth`) and user management (`/users`). Because these routes belong to a plugin rather than a user-created content-type, they cannot be customized with `createCoreRouter`. Instead, you extend them through the [plugin extension system](/cms/plugins-development/plugins-extension) using a `strapi-server.js` or `strapi-server.ts` file in the `./src/extensions/users-permissions/` folder.
+The [Users & Permissions plugin](/cms/features/users-permissions) ships with built-in routes for authentication (`/auth`) and user management (`/users`). Because these routes belong to a plugin rather than a user-created content-type, they cannot be customized with `createCoreRouter`. Instead, extend them through the [plugin extension system](/cms/plugins-development/plugins-extension) using a `strapi-server.js` or `strapi-server.ts` file in the `/src/extensions/users-permissions/` folder.
:::prerequisites
- A Strapi 5 project with the Users & Permissions plugin installed (included by default).
@@ -26,10 +26,14 @@ The [Users & Permissions plugin](/cms/features/users-permissions) ships with bui
## How Users & Permissions routes work {#how-routes-work}
-Unlike content-types you create (e.g., `api::restaurant.restaurant`), the Users & Permissions plugin registers its routes inside the `plugin.routes['content-api'].routes` array. This array contains all `/users`, `/auth`, and `/roles` route definitions.
+
+
+
+Content-types you create (e.g., `api::restaurant.restaurant`) register routes differently. The Users & Permissions plugin registers its routes inside the `plugin.routes['content-api'].routes` array, which contains all `/users`, `/auth`, and `/roles` route definitions.
Each route is an object with the following shape:
+
```js
{
method: 'GET', // HTTP method
@@ -37,25 +41,33 @@ Each route is an object with the following shape:
handler: 'user.find', // controller.action
config: {
prefix: '', // path prefix (empty means /api)
- policies: [], // array of policies to run before the handler
- middlewares: [], // array of middlewares
},
}
```
+Route configs can also include optional `policies` and `middlewares` arrays (see [Add a custom policy to a user route](#add-custom-policy)).
+
+
The available `user` controller actions are: `find`, `findOne`, `create`, `update`, `destroy`, `me`, and `count`.
-The available `auth` controller actions are: `callback` (login), `register`, `forgotPassword`, `resetPassword`, `changePassword`, `emailConfirmation`, `sendEmailConfirmation`, `connect`, and `refresh`.
+
+The available `auth` controller actions are: `callback` (login), `register`, `forgotPassword`, `resetPassword`, `changePassword`, `emailConfirmation`, `sendEmailConfirmation`, `connect`, `refresh`, and `logout`.
+
+:::note Understanding the controller types
+The `user` controller is a plain object with methods, while the `auth` controller is a factory function `({ strapi }) => ({...})` that Strapi resolves lazily. In the plugin extension file, both are accessible on `plugin.controllers`, but they behave differently when overridden. See [Override a controller action](#override-controller) and [Override an auth controller action](#override-auth-route) for the correct pattern for each.
+:::
## Extend routes with strapi-server {#extend-routes}
+
+
All customizations to the Users & Permissions plugin go in a single file:
-```js title="./src/extensions/users-permissions/strapi-server.js"
+```js title="/src/extensions/users-permissions/strapi-server.js"
module.exports = (plugin) => {
// Your customizations here
@@ -67,7 +79,7 @@ module.exports = (plugin) => {
-```ts title="./src/extensions/users-permissions/strapi-server.ts"
+```ts title="/src/extensions/users-permissions/strapi-server.ts"
export default (plugin) => {
// Your customizations here
@@ -83,17 +95,22 @@ The function receives the full plugin object and must return it. You can modify
## Add a custom policy to a user route {#add-custom-policy}
-A common requirement is to restrict who can update or delete user accounts. For example, you might want to ensure that users can only update their own profile.
+A common requirement is restricting who can update or delete user accounts: for example, ensuring users can only update their own profile.
### 1. Create the policy file
-Create a global policy that checks whether the authenticated user matches the target user:
+
+
+
+Create a global policy that checks whether the authenticated user matches the target user. The policy function receives the Koa context (with access to `state.user` and `params`), an optional config object, and `{ strapi }`:
-```js title="./src/policies/is-own-user.js"
+```js title="/src/policies/is-own-user.js"
+"use strict";
+
module.exports = (policyContext, config, { strapi }) => {
const currentUser = policyContext.state.user;
@@ -115,7 +132,7 @@ module.exports = (policyContext, config, { strapi }) => {
-```ts title="./src/policies/is-own-user.ts"
+```ts title="/src/policies/is-own-user.ts"
export default (policyContext, config, { strapi }) => {
const currentUser = policyContext.state.user;
@@ -139,13 +156,16 @@ export default (policyContext, config, { strapi }) => {
### 2. Attach the policy to the user routes
+
+
+
In the plugin extension file, find the `update` and `delete` routes and add the policy:
-```js title="./src/extensions/users-permissions/strapi-server.js"
+```js title="/src/extensions/users-permissions/strapi-server.js"
module.exports = (plugin) => {
// Find the routes that need the policy
const routes = plugin.routes['content-api'].routes;
@@ -180,7 +200,7 @@ module.exports = (plugin) => {
-```ts title="./src/extensions/users-permissions/strapi-server.ts"
+```ts title="/src/extensions/users-permissions/strapi-server.ts"
export default (plugin) => {
// Find the routes that need the policy
const routes = plugin.routes['content-api'].routes;
@@ -215,7 +235,7 @@ export default (plugin) => {
-With this configuration, `PUT /api/users/:id` and `DELETE /api/users/:id` will return a `403 Forbidden` error if the authenticated user does not match the `:id` in the URL.
+With this configuration, `PUT /api/users/:id` and `DELETE /api/users/:id` return a `403 Forbidden` error if the authenticated user does not match the `:id` in the URL.
:::tip
For a more informative error message, throw a `PolicyError` instead of returning `false`:
@@ -233,13 +253,15 @@ See the [policies documentation](/cms/backend-customization/policies) for more d
## Override a controller action {#override-controller}
-You can replace or wrap an existing controller action. For instance, to add custom logic to the `me` endpoint:
+
+
+The `user` controller is a plain object, so you can directly read and replace its methods in the extension file. For instance, to add custom logic to the `me` endpoint:
-```js title="./src/extensions/users-permissions/strapi-server.js"
+```js title="/src/extensions/users-permissions/strapi-server.js"
module.exports = (plugin) => {
const originalMe = plugin.controllers.user.me;
@@ -261,7 +283,7 @@ module.exports = (plugin) => {
-```ts title="./src/extensions/users-permissions/strapi-server.ts"
+```ts title="/src/extensions/users-permissions/strapi-server.ts"
export default (plugin) => {
const originalMe = plugin.controllers.user.me;
@@ -287,15 +309,98 @@ export default (plugin) => {
When wrapping a controller, always call the original function first to preserve the default behavior. Skipping the original function means you take over the full request handling, including sanitization and error handling.
:::
+## Override an auth controller action {#override-auth-route}
+
+
+
+
+The `auth` controller uses a factory pattern: it exports a function `({ strapi }) => ({...})` instead of a plain object. When your extension code runs, Strapi has not yet resolved this factory. As a result, `plugin.controllers.auth` is a function, not an object with methods.
+
+To override an auth action, wrap the factory itself:
+
+
+
+
+
+```js title="/src/extensions/users-permissions/strapi-server.js"
+module.exports = (plugin) => {
+ const originalAuthFactory = plugin.controllers.auth;
+
+ plugin.controllers.auth = ({ strapi }) => {
+ // Resolve the original factory to get the controller methods
+ const originalAuth = originalAuthFactory({ strapi });
+
+ // Override the register method
+ const originalRegister = originalAuth.register;
+
+ originalAuth.register = async (ctx) => {
+ // Call the original register logic
+ await originalRegister(ctx);
+
+ // Custom post-registration logic
+ if (ctx.body && ctx.body.user) {
+ strapi.log.info(`New user registered: ${ctx.body.user.email}`);
+ }
+ };
+
+ return originalAuth;
+ };
+
+ return plugin;
+};
+```
+
+
+
+
+
+```ts title="/src/extensions/users-permissions/strapi-server.ts"
+export default (plugin) => {
+ const originalAuthFactory = plugin.controllers.auth;
+
+ plugin.controllers.auth = ({ strapi }) => {
+ // Resolve the original factory to get the controller methods
+ const originalAuth = originalAuthFactory({ strapi });
+
+ // Override the register method
+ const originalRegister = originalAuth.register;
+
+ originalAuth.register = async (ctx) => {
+ // Call the original register logic
+ await originalRegister(ctx);
+
+ // Custom post-registration logic
+ if (ctx.body && ctx.body.user) {
+ strapi.log.info(`New user registered: ${ctx.body.user.email}`);
+ }
+ };
+
+ return originalAuth;
+ };
+
+ return plugin;
+};
+```
+
+
+
+
+
+:::caution
+Do not access `plugin.controllers.auth.register` directly. Because `auth` is a factory function at extension time, its methods are not accessible until Strapi calls the factory. Always wrap the factory as shown above.
+:::
+
## Add a new route {#add-new-route}
You can add custom routes to the Users & Permissions plugin. For example, to add an endpoint that deactivates a user account:
+
+
-```js title="./src/extensions/users-permissions/strapi-server.js"
+```js title="/src/extensions/users-permissions/strapi-server.js"
module.exports = (plugin) => {
// Add a new controller action
plugin.controllers.user.deactivate = async (ctx) => {
@@ -328,7 +433,7 @@ module.exports = (plugin) => {
-```ts title="./src/extensions/users-permissions/strapi-server.ts"
+```ts title="/src/extensions/users-permissions/strapi-server.ts"
export default (plugin) => {
// Add a new controller action
plugin.controllers.user.deactivate = async (ctx) => {
@@ -363,58 +468,6 @@ export default (plugin) => {
After restarting Strapi, `POST /api/users/:id/deactivate` becomes available. Grant the corresponding permission in the admin panel under *Users & Permissions plugin > Roles* for the roles that should access this endpoint.
-## Override an auth route {#override-auth-route}
-
-Authentication routes can be customized in the same way. For example, to add a rate-limiting middleware to the login endpoint or to run custom logic after registration:
-
-
-
-
-
-```js title="./src/extensions/users-permissions/strapi-server.js"
-module.exports = (plugin) => {
- const originalRegister = plugin.controllers.auth.register;
-
- plugin.controllers.auth.register = async (ctx) => {
- // Call the original register logic
- await originalRegister(ctx);
-
- // Custom post-registration logic
- if (ctx.body && ctx.body.user) {
- strapi.log.info(`New user registered: ${ctx.body.user.email}`);
- }
- };
-
- return plugin;
-};
-```
-
-
-
-
-
-```ts title="./src/extensions/users-permissions/strapi-server.ts"
-export default (plugin) => {
- const originalRegister = plugin.controllers.auth.register;
-
- plugin.controllers.auth.register = async (ctx) => {
- // Call the original register logic
- await originalRegister(ctx);
-
- // Custom post-registration logic
- if (ctx.body && ctx.body.user) {
- strapi.log.info(`New user registered: ${ctx.body.user.email}`);
- }
- };
-
- return plugin;
-};
-```
-
-
-
-
-
## Remove a route {#remove-route}
You can disable a route by filtering it out of the routes array. For example, to disable the user count endpoint:
@@ -423,7 +476,7 @@ You can disable a route by filtering it out of the routes array. For example, to
-```js title="./src/extensions/users-permissions/strapi-server.js"
+```js title="/src/extensions/users-permissions/strapi-server.js"
module.exports = (plugin) => {
plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter(
(route) => route.handler !== 'user.count'
@@ -437,7 +490,7 @@ module.exports = (plugin) => {
-```ts title="./src/extensions/users-permissions/strapi-server.ts"
+```ts title="/src/extensions/users-permissions/strapi-server.ts"
export default (plugin) => {
plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter(
(route) => route.handler !== 'user.count'
@@ -459,7 +512,7 @@ In practice, you often combine several customizations in the same file. The foll
-```js title="./src/extensions/users-permissions/strapi-server.js"
+```js title="/src/extensions/users-permissions/strapi-server.js"
module.exports = (plugin) => {
const routes = plugin.routes['content-api'].routes;
@@ -518,7 +571,7 @@ module.exports = (plugin) => {
-```ts title="./src/extensions/users-permissions/strapi-server.ts"
+```ts title="/src/extensions/users-permissions/strapi-server.ts"
export default (plugin) => {
const routes = plugin.routes['content-api'].routes;
@@ -590,8 +643,9 @@ After making changes, restart Strapi and verify your customizations:
| Symptom | Possible cause |
| ------- | -------------- |
-| Route not found (404) | The route was not pushed to `plugin.routes['content-api'].routes`, or the `prefix` property is missing. |
+| Route not found (404) | The new route was not pushed to `plugin.routes['content-api'].routes`, or its `prefix` property is missing. |
| Policy not applied | The policy name is incorrect. Global policies require the `global::` prefix (e.g., `global::is-own-user`). |
| Controller returns 500 | The controller action name does not match the `handler` value in the route definition. |
| Changes not reflected | Strapi was not restarted after modifying the extension file. Extensions are loaded at startup. |
| Permission denied (403) | The new action is not enabled for the role. Enable it in *Users & Permissions plugin > Roles*. |
+| Cannot read property of `auth` controller | The `auth` controller is a factory function, not a plain object. Wrap the factory instead of accessing methods directly (see [Override an auth controller action](#override-auth-route)). |
diff --git a/docusaurus/docs/cms/features/users-permissions.md b/docusaurus/docs/cms/features/users-permissions.md
index fd09eefe4b..2124281ffa 100644
--- a/docusaurus/docs/cms/features/users-permissions.md
+++ b/docusaurus/docs/cms/features/users-permissions.md
@@ -1,6 +1,7 @@
---
title: Users & Permissions
description: Learn to use the Users & Permissions and API tokens features to manage end-users.
+displayed_sidebar: cmsSidebar
toc_max_heading_level: 5
tags:
- admin panel
@@ -588,6 +589,41 @@ When [session management](#jwt-management-modes) is enabled (`jwtManagement: 're
| `POST` | `/api/auth/refresh` | Refresh access token using refresh token |
| `POST` | `/api/auth/logout` | Revoke user sessions (supports device-specific logout) |
+To refresh your authentication token you could for instance send the following request:
+
+
+
+```
+curl -X POST http://localhost:1337/api/auth/refresh \
+ -H "Content-Type: application/json" \
+ -d '{
+ "refreshToken": "your-refresh-token"
+ }'
+```
+
+
+
+```json
+{
+ "jwt": "your-new-access-token"
+}
+```
+
+
+
+To log out of all sessions, send the following request:
+
+
+
+
+```bash
+curl -X POST http://localhost:1337/api/auth/logout \
+ -H "Authorization: Bearer your-access-token"
+```
+
+
+
+
#### User CRUD endpoints
The Users & Permissions feature also exposes a set of endpoints for managing user records directly. These endpoints are separate from the authentication endpoints and allow you to create, read, update, and delete user entries:
@@ -603,12 +639,12 @@ The Users & Permissions feature also exposes a set of endpoints for managing use
| `DELETE` | `/api/users/:id` | Delete a user by ID |
:::note
-These endpoints are protected by the role-based permission system. To access them, enable the corresponding action (e.g., `find`, `findOne`, `create`, `update`, `delete`) for the desired role in *Users & Permissions plugin > Roles*.
+These endpoints are protected by the role-based permission system. To access them, enable the corresponding action (e.g., `find`, `findOne`, `create`, `update`, `destroy`, `me`, `count`) for the desired role in *Users & Permissions plugin > Roles*.
:::
##### Get the authenticated user
-The `GET /api/users/me` endpoint returns the user associated with the current JWT. This is useful for front-end applications that need to display user profile information after login.
+The `GET /api/users/me` endpoint returns the user associated with the current JWT. The endpoint is useful for front-end applications that need to display user profile information after login.
@@ -632,7 +668,8 @@ curl -X GET http://localhost:1337/api/users/me \
"confirmed": true,
"blocked": false,
"createdAt": "2024-01-15T09:00:00.000Z",
- "updatedAt": "2024-01-15T09:00:00.000Z"
+ "updatedAt": "2024-01-15T09:00:00.000Z",
+ "publishedAt": "2024-01-15T09:00:00.000Z"
}
```
@@ -641,7 +678,7 @@ curl -X GET http://localhost:1337/api/users/me \
##### Find all users
-The `GET /api/users` endpoint returns a list of all users. Populate and filter parameters can be passed as query strings.
+The `GET /api/users` endpoint returns a list of all users. [`populate` and `filters` parameters](/cms/api/rest/parameters) can be passed as query strings.
@@ -669,10 +706,13 @@ curl -X GET "http://localhost:1337/api/users?populate=role" \
"id": 1,
"name": "Authenticated",
"description": "Default role given to authenticated user.",
- "type": "authenticated"
+ "type": "authenticated",
+ "createdAt": "2024-01-01T00:00:00.000Z",
+ "updatedAt": "2024-01-01T00:00:00.000Z"
},
"createdAt": "2024-01-15T09:00:00.000Z",
- "updatedAt": "2024-01-15T09:00:00.000Z"
+ "updatedAt": "2024-01-15T09:00:00.000Z",
+ "publishedAt": "2024-01-15T09:00:00.000Z"
}
]
```
@@ -682,7 +722,7 @@ curl -X GET "http://localhost:1337/api/users?populate=role" \
##### Create a user
-The `POST /api/users` endpoint creates a new user. Unlike `/api/auth/local/register`, this endpoint requires the caller to have the `create` permission for the Users & Permissions plugin.
+The `POST /api/users` endpoint creates a new user. Unlike `/api/auth/local/register`, this endpoint requires `create` permission for the Users & Permissions plugin.
@@ -714,7 +754,8 @@ curl -X POST http://localhost:1337/api/users \
"confirmed": true,
"blocked": false,
"createdAt": "2024-01-16T10:00:00.000Z",
- "updatedAt": "2024-01-16T10:00:00.000Z"
+ "updatedAt": "2024-01-16T10:00:00.000Z",
+ "publishedAt": "2024-01-16T10:00:00.000Z"
}
```
@@ -751,7 +792,8 @@ curl -X PUT http://localhost:1337/api/users/2 \
"confirmed": true,
"blocked": false,
"createdAt": "2024-01-16T10:00:00.000Z",
- "updatedAt": "2024-01-17T11:00:00.000Z"
+ "updatedAt": "2024-01-17T11:00:00.000Z",
+ "publishedAt": "2024-01-16T10:00:00.000Z"
}
```
@@ -784,48 +826,14 @@ curl -X DELETE http://localhost:1337/api/users/2 \
"confirmed": true,
"blocked": false,
"createdAt": "2024-01-16T10:00:00.000Z",
- "updatedAt": "2024-01-17T11:00:00.000Z"
+ "updatedAt": "2024-01-17T11:00:00.000Z",
+ "publishedAt": "2024-01-16T10:00:00.000Z"
}
```
-To refresh your authentication token you could for instance send the following request:
-
-
-
-```
-curl -X POST http://localhost:1337/api/auth/refresh \
- -H "Content-Type: application/json" \
- -d '{
- "refreshToken": "your-refresh-token"
- }'
-```
-
-
-
-```json
-{
- "jwt": "your-new-access-token"
-}
-```
-
-
-
-To log out of all sessions, send the following request:
-
-
-
-
-```bash
-curl -X POST http://localhost:1337/api/auth/logout \
- -H "Authorization: Bearer your-access-token"
-```
-
-
-
-
#### Identifier
The `identifier` parameter sent with requests can be an email or username, as in the following examples:
diff --git a/docusaurus/sidebars.js b/docusaurus/sidebars.js
index c2d123492d..9646764e08 100644
--- a/docusaurus/sidebars.js
+++ b/docusaurus/sidebars.js
@@ -364,6 +364,15 @@ const sidebars = {
'cms/backend-customization/services',
'cms/backend-customization/models',
'cms/backend-customization/webhooks',
+ {
+ type: 'category',
+ label: 'Guides',
+ collapsible: true,
+ collapsed: true,
+ items: [
+ 'cms/backend-customization/guides/customizing-users-permissions-plugin-routes',
+ ],
+ },
],
},
{
diff --git a/docusaurus/static/llms-code.txt b/docusaurus/static/llms-code.txt
index cef74b2611..8cd63048b2 100644
--- a/docusaurus/static/llms-code.txt
+++ b/docusaurus/static/llms-code.txt
@@ -12228,6 +12228,532 @@ module.exports = createCoreController('api::review.review', ({ strapi }) => ({
+# Customizing Users & Permissions plugin routes
+Source: https://docs.strapi.io/cms/backend-customization/guides/customizing-users-permissions-plugin-routes
+
+## How Users & Permissions routes work
+Description: Code example from "How Users & Permissions routes work"
+(Source: https://docs.strapi.io/cms/backend-customization/guides/customizing-users-permissions-plugin-routes#how-routes-work)
+
+Language: JSON
+File path: /plugins/users-permissions/server/routes/content-api/user.js
+
+```json
+{
+ method: 'GET', // HTTP method
+ path: '/users', // URL path (relative to /api)
+ handler: 'user.find', // controller.action
+ config: {
+ prefix: '', // path prefix (empty means /api)
+ },
+}
+```
+
+
+## Extend routes with strapi-server
+Description: All customizations to the Users & Permissions plugin go in a single file:
+(Source: https://docs.strapi.io/cms/backend-customization/guides/customizing-users-permissions-plugin-routes#extend-routes)
+
+Language: JavaScript
+File path: /src/extensions/users-permissions/strapi-server.js
+
+```js
+module.exports = (plugin) => {
+ // Your customizations here
+
+ return plugin;
+};
+```
+
+---
+Language: TypeScript
+File path: /src/extensions/users-permissions/strapi-server.ts
+
+```ts
+export default (plugin) => {
+ // Your customizations here
+
+ return plugin;
+};
+```
+
+
+## 1. Create the policy file
+Description: Create a global policy that checks whether the authenticated user matches the target user.
+(Source: https://docs.strapi.io/cms/backend-customization/guides/customizing-users-permissions-plugin-routes#1-create-the-policy-file)
+
+Language: JavaScript
+File path: /src/policies/is-own-user.js
+
+```js
+"use strict";
+
+module.exports = (policyContext, config, { strapi }) => {
+ const currentUser = policyContext.state.user;
+
+ if (!currentUser) {
+ return false;
+ }
+
+ const targetUserId = Number(policyContext.params.id);
+
+ if (currentUser.id !== targetUserId) {
+ return false;
+ }
+
+ return true;
+};
+```
+
+---
+Language: TypeScript
+File path: /src/policies/is-own-user.ts
+
+```ts
+export default (policyContext, config, { strapi }) => {
+ const currentUser = policyContext.state.user;
+
+ if (!currentUser) {
+ return false;
+ }
+
+ const targetUserId = Number(policyContext.params.id);
+
+ if (currentUser.id !== targetUserId) {
+ return false;
+ }
+
+ return true;
+};
+```
+
+
+## 2. Attach the policy to the user routes
+Description: In the plugin extension file, find the update and delete routes and add the policy:
+(Source: https://docs.strapi.io/cms/backend-customization/guides/customizing-users-permissions-plugin-routes#2-attach-the-policy-to-the-user-routes)
+
+Language: JavaScript
+File path: /src/extensions/users-permissions/strapi-server.js
+
+```js
+module.exports = (plugin) => {
+ // Find the routes that need the policy
+ const routes = plugin.routes['content-api'].routes;
+
+ // Add the 'is-own-user' policy to the update route
+ const updateRoute = routes.find(
+ (route) => route.handler === 'user.update'
+ );
+
+ if (updateRoute) {
+ updateRoute.config = updateRoute.config || {};
+ updateRoute.config.policies = updateRoute.config.policies || [];
+ updateRoute.config.policies.push('global::is-own-user');
+ }
+
+ // Add the same policy to the delete route
+ const deleteRoute = routes.find(
+ (route) => route.handler === 'user.destroy'
+ );
+
+ if (deleteRoute) {
+ deleteRoute.config = deleteRoute.config || {};
+ deleteRoute.config.policies = deleteRoute.config.policies || [];
+ deleteRoute.config.policies.push('global::is-own-user');
+ }
+
+ return plugin;
+};
+```
+
+---
+Language: TypeScript
+File path: /src/extensions/users-permissions/strapi-server.ts
+
+```ts
+export default (plugin) => {
+ // Find the routes that need the policy
+ const routes = plugin.routes['content-api'].routes;
+
+ // Add the 'is-own-user' policy to the update route
+ const updateRoute = routes.find(
+ (route) => route.handler === 'user.update'
+ );
+
+ if (updateRoute) {
+ updateRoute.config = updateRoute.config || {};
+ updateRoute.config.policies = updateRoute.config.policies || [];
+ updateRoute.config.policies.push('global::is-own-user');
+ }
+
+ // Add the same policy to the delete route
+ const deleteRoute = routes.find(
+ (route) => route.handler === 'user.destroy'
+ );
+
+ if (deleteRoute) {
+ deleteRoute.config = deleteRoute.config || {};
+ deleteRoute.config.policies = deleteRoute.config.policies || [];
+ deleteRoute.config.policies.push('global::is-own-user');
+ }
+
+ return plugin;
+};
+```
+
+Language: TypeScript
+File path: /api/core/strapi/plugin-routes-extension-bc.test.api.ts
+
+```ts
+const { errors } = require('@strapi/utils');
+const { PolicyError } = errors;
+
+// Inside the policy:
+throw new PolicyError('You can only modify your own account');
+```
+
+
+## Override a controller action
+Description: The user controller is a plain object, so you can directly read and replace its methods in the extension file.
+(Source: https://docs.strapi.io/cms/backend-customization/guides/customizing-users-permissions-plugin-routes#override-controller)
+
+Language: JavaScript
+File path: /src/extensions/users-permissions/strapi-server.js
+
+```js
+module.exports = (plugin) => {
+ const originalMe = plugin.controllers.user.me;
+
+ plugin.controllers.user.me = async (ctx) => {
+ // Call the original controller
+ await originalMe(ctx);
+
+ // Add extra data to the response
+ if (ctx.body) {
+ ctx.body.timestamp = new Date().toISOString();
+ }
+ };
+
+ return plugin;
+};
+```
+
+---
+Language: TypeScript
+File path: /src/extensions/users-permissions/strapi-server.ts
+
+```ts
+export default (plugin) => {
+ const originalMe = plugin.controllers.user.me;
+
+ plugin.controllers.user.me = async (ctx) => {
+ // Call the original controller
+ await originalMe(ctx);
+
+ // Add extra data to the response
+ if (ctx.body) {
+ ctx.body.timestamp = new Date().toISOString();
+ }
+ };
+
+ return plugin;
+};
+```
+
+
+## Override an auth controller action
+Description: To override an auth action, wrap the factory itself:
+(Source: https://docs.strapi.io/cms/backend-customization/guides/customizing-users-permissions-plugin-routes#override-auth-route)
+
+Language: JavaScript
+File path: /src/extensions/users-permissions/strapi-server.js
+
+```js
+module.exports = (plugin) => {
+ const originalAuthFactory = plugin.controllers.auth;
+
+ plugin.controllers.auth = ({ strapi }) => {
+ // Resolve the original factory to get the controller methods
+ const originalAuth = originalAuthFactory({ strapi });
+
+ // Override the register method
+ const originalRegister = originalAuth.register;
+
+ originalAuth.register = async (ctx) => {
+ // Call the original register logic
+ await originalRegister(ctx);
+
+ // Custom post-registration logic
+ if (ctx.body && ctx.body.user) {
+ strapi.log.info(`New user registered: ${ctx.body.user.email}`);
+ }
+ };
+
+ return originalAuth;
+ };
+
+ return plugin;
+};
+```
+
+---
+Language: TypeScript
+File path: /src/extensions/users-permissions/strapi-server.ts
+
+```ts
+export default (plugin) => {
+ const originalAuthFactory = plugin.controllers.auth;
+
+ plugin.controllers.auth = ({ strapi }) => {
+ // Resolve the original factory to get the controller methods
+ const originalAuth = originalAuthFactory({ strapi });
+
+ // Override the register method
+ const originalRegister = originalAuth.register;
+
+ originalAuth.register = async (ctx) => {
+ // Call the original register logic
+ await originalRegister(ctx);
+
+ // Custom post-registration logic
+ if (ctx.body && ctx.body.user) {
+ strapi.log.info(`New user registered: ${ctx.body.user.email}`);
+ }
+ };
+
+ return originalAuth;
+ };
+
+ return plugin;
+};
+```
+
+
+## Add a new route
+Description: Code example from "Add a new route"
+(Source: https://docs.strapi.io/cms/backend-customization/guides/customizing-users-permissions-plugin-routes#add-new-route)
+
+Language: JavaScript
+File path: /src/extensions/users-permissions/strapi-server.js
+
+```js
+module.exports = (plugin) => {
+ // Add a new controller action
+ plugin.controllers.user.deactivate = async (ctx) => {
+ const { id } = ctx.params;
+
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .edit(id, { blocked: true });
+
+ ctx.body = { message: `User ${user.username} has been deactivated` };
+ };
+
+ // Register the route
+ plugin.routes['content-api'].routes.push({
+ method: 'POST',
+ path: '/users/:id/deactivate',
+ handler: 'user.deactivate',
+ config: {
+ prefix: '',
+ policies: ['global::is-own-user'],
+ },
+ });
+
+ return plugin;
+};
+```
+
+---
+Language: TypeScript
+File path: /src/extensions/users-permissions/strapi-server.ts
+
+```ts
+export default (plugin) => {
+ // Add a new controller action
+ plugin.controllers.user.deactivate = async (ctx) => {
+ const { id } = ctx.params;
+
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .edit(id, { blocked: true });
+
+ ctx.body = { message: `User ${user.username} has been deactivated` };
+ };
+
+ // Register the route
+ plugin.routes['content-api'].routes.push({
+ method: 'POST',
+ path: '/users/:id/deactivate',
+ handler: 'user.deactivate',
+ config: {
+ prefix: '',
+ policies: ['global::is-own-user'],
+ },
+ });
+
+ return plugin;
+};
+```
+
+
+## Remove a route
+Description: Disable a route by filtering it out of the routes array.
+(Source: https://docs.strapi.io/cms/backend-customization/guides/customizing-users-permissions-plugin-routes#remove-route)
+
+Language: JavaScript
+File path: /src/extensions/users-permissions/strapi-server.js
+
+```js
+module.exports = (plugin) => {
+ plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter(
+ (route) => route.handler !== 'user.count'
+ );
+
+ return plugin;
+};
+```
+
+---
+Language: TypeScript
+File path: /src/extensions/users-permissions/strapi-server.ts
+
+```ts
+export default (plugin) => {
+ plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter(
+ (route) => route.handler !== 'user.count'
+ );
+
+ return plugin;
+};
+```
+
+
+## Combine multiple customizations
+Description: In practice, you often combine several customizations in the same file.
+(Source: https://docs.strapi.io/cms/backend-customization/guides/customizing-users-permissions-plugin-routes#combine-customizations)
+
+Language: JavaScript
+File path: /src/extensions/users-permissions/strapi-server.js
+
+```js
+module.exports = (plugin) => {
+ const routes = plugin.routes['content-api'].routes;
+
+ // 1. Add 'is-own-user' policy to update and delete
+ for (const route of routes) {
+ if (route.handler === 'user.update' || route.handler === 'user.destroy') {
+ route.config = route.config || {};
+ route.config.policies = route.config.policies || [];
+ route.config.policies.push('global::is-own-user');
+ }
+ }
+
+ // 2. Wrap the 'me' controller to include the user's role
+ const originalMe = plugin.controllers.user.me;
+
+ plugin.controllers.user.me = async (ctx) => {
+ await originalMe(ctx);
+
+ if (ctx.state.user && ctx.body) {
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .fetch(ctx.state.user.id, { populate: ['role'] });
+
+ ctx.body.role = user.role;
+ }
+ };
+
+ // 3. Add a custom route
+ plugin.controllers.user.profile = async (ctx) => {
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .fetch(ctx.state.user.id, { populate: ['role'] });
+
+ ctx.body = {
+ username: user.username,
+ email: user.email,
+ role: user.role?.name,
+ createdAt: user.createdAt,
+ };
+ };
+
+ routes.push({
+ method: 'GET',
+ path: '/users/profile',
+ handler: 'user.profile',
+ config: { prefix: '' },
+ });
+
+ return plugin;
+};
+```
+
+---
+Language: TypeScript
+File path: /src/extensions/users-permissions/strapi-server.ts
+
+```ts
+export default (plugin) => {
+ const routes = plugin.routes['content-api'].routes;
+
+ // 1. Add 'is-own-user' policy to update and delete
+ for (const route of routes) {
+ if (route.handler === 'user.update' || route.handler === 'user.destroy') {
+ route.config = route.config || {};
+ route.config.policies = route.config.policies || [];
+ route.config.policies.push('global::is-own-user');
+ }
+ }
+
+ // 2. Wrap the 'me' controller to include the user's role
+ const originalMe = plugin.controllers.user.me;
+
+ plugin.controllers.user.me = async (ctx) => {
+ await originalMe(ctx);
+
+ if (ctx.state.user && ctx.body) {
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .fetch(ctx.state.user.id, { populate: ['role'] });
+
+ ctx.body.role = user.role;
+ }
+ };
+
+ // 3. Add a custom route
+ plugin.controllers.user.profile = async (ctx) => {
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .fetch(ctx.state.user.id, { populate: ['role'] });
+
+ ctx.body = {
+ username: user.username,
+ email: user.email,
+ role: user.role?.name,
+ createdAt: user.createdAt,
+ };
+ };
+
+ routes.push({
+ method: 'GET',
+ path: '/users/profile',
+ handler: 'user.profile',
+ config: { prefix: '' },
+ });
+
+ return plugin;
+};
+```
+
+
+
# Middlewares
Source: https://docs.strapi.io/cms/backend-customization/middlewares
@@ -23424,10 +23950,188 @@ File path: /config/plugins.js|ts
```
-## Session management endpoints
-Description: Code example from "Session management endpoints"
-(Source: https://docs.strapi.io/cms/features/users-permissions#session-management-endpoints)
+## Get the authenticated user
+Description: Code example from "Get the authenticated user"
+(Source: https://docs.strapi.io/cms/features/users-permissions#get-the-authenticated-user)
+
+Language: Bash
+File path: N/A
+```bash
+curl -X GET http://localhost:1337/api/users/me \
+ -H "Authorization: Bearer your-access-token"
+```
+
+---
+Language: JSON
+File path: N/A
+
+```json
+{
+ "id": 1,
+ "documentId": "abc123",
+ "username": "kai",
+ "email": "kai@strapi.io",
+ "provider": "local",
+ "confirmed": true,
+ "blocked": false,
+ "createdAt": "2024-01-15T09:00:00.000Z",
+ "updatedAt": "2024-01-15T09:00:00.000Z",
+ "publishedAt": "2024-01-15T09:00:00.000Z"
+}
+```
+
+
+## Find all users
+Description: Code example from "Find all users"
+(Source: https://docs.strapi.io/cms/features/users-permissions#find-all-users)
+
+Language: Bash
+File path: N/A
+
+```bash
+curl -X GET "http://localhost:1337/api/users?populate=role" \
+ -H "Authorization: Bearer your-access-token"
+```
+
+---
+Language: JSON
+File path: N/A
+
+```json
+[
+ {
+ "id": 1,
+ "documentId": "abc123",
+ "username": "kai",
+ "email": "kai@strapi.io",
+ "provider": "local",
+ "confirmed": true,
+ "blocked": false,
+ "role": {
+ "id": 1,
+ "name": "Authenticated",
+ "description": "Default role given to authenticated user.",
+ "type": "authenticated",
+ "createdAt": "2024-01-01T00:00:00.000Z",
+ "updatedAt": "2024-01-01T00:00:00.000Z"
+ },
+ "createdAt": "2024-01-15T09:00:00.000Z",
+ "updatedAt": "2024-01-15T09:00:00.000Z",
+ "publishedAt": "2024-01-15T09:00:00.000Z"
+ }
+]
+```
+
+
+## Create a user
+Description: Code example from "Create a user"
+(Source: https://docs.strapi.io/cms/features/users-permissions#create-a-user)
+
+Language: Bash
+File path: N/A
+
+```bash
+curl -X POST http://localhost:1337/api/users \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer your-access-token" \
+ -d '{
+ "username": "newuser",
+ "email": "newuser@strapi.io",
+ "password": "Password123",
+ "role": 1,
+ "confirmed": true
+ }'
+```
+
+---
+Language: JSON
+File path: N/A
+
+```json
+{
+ "id": 2,
+ "documentId": "def456",
+ "username": "newuser",
+ "email": "newuser@strapi.io",
+ "provider": "local",
+ "confirmed": true,
+ "blocked": false,
+ "createdAt": "2024-01-16T10:00:00.000Z",
+ "updatedAt": "2024-01-16T10:00:00.000Z",
+ "publishedAt": "2024-01-16T10:00:00.000Z"
+}
+```
+
+
+## Update a user
+Description: Code example from "Update a user"
+(Source: https://docs.strapi.io/cms/features/users-permissions#update-a-user)
+
+Language: Bash
+File path: N/A
+
+```bash
+curl -X PUT http://localhost:1337/api/users/2 \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer your-access-token" \
+ -d '{
+ "username": "updateduser"
+ }'
+```
+
+---
+Language: JSON
+File path: N/A
+
+```json
+{
+ "id": 2,
+ "documentId": "def456",
+ "username": "updateduser",
+ "email": "newuser@strapi.io",
+ "provider": "local",
+ "confirmed": true,
+ "blocked": false,
+ "createdAt": "2024-01-16T10:00:00.000Z",
+ "updatedAt": "2024-01-17T11:00:00.000Z",
+ "publishedAt": "2024-01-16T10:00:00.000Z"
+}
+```
+
+
+## Delete a user
+Description: Code example from "Delete a user"
+(Source: https://docs.strapi.io/cms/features/users-permissions#delete-a-user)
+
+Language: Bash
+File path: N/A
+
+```bash
+curl -X DELETE http://localhost:1337/api/users/2 \
+ -H "Authorization: Bearer your-access-token"
+```
+
+---
+Language: JSON
+File path: N/A
+
+```json
+{
+ "id": 2,
+ "documentId": "def456",
+ "username": "updateduser",
+ "email": "newuser@strapi.io",
+ "provider": "local",
+ "confirmed": true,
+ "blocked": false,
+ "createdAt": "2024-01-16T10:00:00.000Z",
+ "updatedAt": "2024-01-17T11:00:00.000Z",
+ "publishedAt": "2024-01-16T10:00:00.000Z"
+}
+```
+
+---
Language: JavaScript
File path: N/A
diff --git a/docusaurus/static/llms-full.txt b/docusaurus/static/llms-full.txt
index 7d96e06f36..af59959f05 100644
--- a/docusaurus/static/llms-full.txt
+++ b/docusaurus/static/llms-full.txt
@@ -4938,6 +4938,166 @@ To list all the available controllers, run `yarn strapi controllers:list`.
+# Customizing Users & Permissions plugin routes
+Source: https://docs.strapi.io/cms/backend-customization/guides/customizing-users-permissions-plugin-routes
+
+# Customizing Users & Permissions plugin routes
+
+The [Users & Permissions plugin](/cms/features/users-permissions) ships with built-in routes for authentication (`/auth`) and user management (`/users`). Because these routes belong to a plugin rather than a user-created content-type, they cannot be customized with `createCoreRouter`. Instead, extend them through the [plugin extension system](/cms/plugins-development/plugins-extension) using a `strapi-server.js` or `strapi-server.ts` file in the `/src/extensions/users-permissions/` folder.
+
+:::prerequisites
+- A Strapi 5 project with the Users & Permissions plugin installed (included by default).
+- Familiarity with [routes](/cms/backend-customization/routes) and [policies](/cms/backend-customization/policies).
+:::
+
+## How Users & Permissions routes work {#how-routes-work}
+
+
+
+
+Content-types you create (e.g., `api::restaurant.restaurant`) register routes differently. The Users & Permissions plugin registers its routes inside the `plugin.routes['content-api'].routes` array, which contains all `/users`, `/auth`, and `/roles` route definitions.
+
+Each route is an object with the following shape:
+
+
+```js
+{
+ method: 'GET', // HTTP method
+ path: '/users', // URL path (relative to /api)
+ handler: 'user.find', // controller.action
+ config: {
+ prefix: '', // path prefix (empty means /api)
+ },
+}
+```
+
+Route configs can also include optional `policies` and `middlewares` arrays (see [Add a custom policy to a user route](#add-custom-policy)).
+
+
+The available `user` controller actions are: `find`, `findOne`, `create`, `update`, `destroy`, `me`, and `count`.
+
+
+The available `auth` controller actions are: `callback` (login), `register`, `forgotPassword`, `resetPassword`, `changePassword`, `emailConfirmation`, `sendEmailConfirmation`, `connect`, `refresh`, and `logout`.
+
+:::note Understanding the controller types
+The `user` controller is a plain object with methods, while the `auth` controller is a factory function `({ strapi }) => ({...})` that Strapi resolves lazily. In the plugin extension file, both are accessible on `plugin.controllers`, but they behave differently when overridden. See [Override a controller action](#override-controller) and [Override an auth controller action](#override-auth-route) for the correct pattern for each.
+:::
+
+## Extend routes with strapi-server {#extend-routes}
+
+
+
+All customizations to the Users & Permissions plugin go in a single file:
+
+
+
+The function receives the full plugin object and must return it. You can modify `plugin.routes`, `plugin.controllers`, `plugin.policies`, and `plugin.services` before returning.
+
+## Add a custom policy to a user route {#add-custom-policy}
+
+A common requirement is restricting who can update or delete user accounts: for example, ensuring users can only update their own profile.
+
+### 1. Create the policy file
+
+
+
+
+Create a global policy that checks whether the authenticated user matches the target user. The policy function receives the Koa context (with access to `state.user` and `params`), an optional config object, and `{ strapi }`:
+
+
+
+### 2. Attach the policy to the user routes
+
+
+
+
+In the plugin extension file, find the `update` and `delete` routes and add the policy:
+
+
+
+With this configuration, `PUT /api/users/:id` and `DELETE /api/users/:id` return a `403 Forbidden` error if the authenticated user does not match the `:id` in the URL.
+
+:::tip
+For a more informative error message, throw a `PolicyError` instead of returning `false`:
+
+```js
+const { errors } = require('@strapi/utils');
+const { PolicyError } = errors;
+
+// Inside the policy:
+throw new PolicyError('You can only modify your own account');
+```
+
+See the [policies documentation](/cms/backend-customization/policies) for more details.
+:::
+
+## Override a controller action {#override-controller}
+
+
+
+The `user` controller is a plain object, so you can directly read and replace its methods in the extension file. For instance, to add custom logic to the `me` endpoint:
+
+
+
+:::caution
+When wrapping a controller, always call the original function first to preserve the default behavior. Skipping the original function means you take over the full request handling, including sanitization and error handling.
+:::
+
+## Override an auth controller action {#override-auth-route}
+
+
+
+
+The `auth` controller uses a factory pattern: it exports a function `({ strapi }) => ({...})` instead of a plain object. When your extension code runs, Strapi has not yet resolved this factory. As a result, `plugin.controllers.auth` is a function, not an object with methods.
+
+To override an auth action, wrap the factory itself:
+
+
+
+:::caution
+Do not access `plugin.controllers.auth.register` directly. Because `auth` is a factory function at extension time, its methods are not accessible until Strapi calls the factory. Always wrap the factory as shown above.
+:::
+
+## Add a new route {#add-new-route}
+
+You can add custom routes to the Users & Permissions plugin. For example, to add an endpoint that deactivates a user account:
+
+
+
+
+
+After restarting Strapi, `POST /api/users/:id/deactivate` becomes available. Grant the corresponding permission in the admin panel under
+
+
+
+## Combine multiple customizations {#combine-customizations}
+
+In practice, you often combine several customizations in the same file. The following example adds a policy to `update` and `delete`, wraps the `me` controller, and adds a new route:
+
+
+
+## Validation
+
+After making changes, restart Strapi and verify your customizations:
+
+1. Run `yarn strapi routes:list` to confirm your new or modified routes appear.
+2. Test protected routes without authentication to verify policies return `403 Forbidden`.
+3. Test with an authenticated user to confirm the expected behavior.
+4. Check the Strapi server logs for errors during startup.
+
+## Troubleshooting
+
+| Symptom | Possible cause |
+| ------- | -------------- |
+| Route not found (404) | The new route was not pushed to `plugin.routes['content-api'].routes`, or its `prefix` property is missing. |
+| Policy not applied | The policy name is incorrect. Global policies require the `global::` prefix (e.g., `global::is-own-user`). |
+| Controller returns 500 | The controller action name does not match the `handler` value in the route definition. |
+| Changes not reflected | Strapi was not restarted after modifying the extension file. Extensions are loaded at startup. |
+| Permission denied (403) | The new action is not enabled for the role. Enable it in *Users & Permissions plugin > Roles*. |
+| Cannot read property of `auth` controller | The `auth` controller is a factory function, not a plain object. Wrap the factory instead of accessing methods directly (see [Override an auth controller action](#override-auth-route)). |
+
+
+
# Middlewares
Source: https://docs.strapi.io/cms/backend-customization/middlewares
@@ -9267,6 +9427,34 @@ By default, Strapi SSO only redirects to the redirect URL that is exactly equal
+##### Find all users
+
+The `GET /api/users` endpoint returns a list of all users. [`populate` and `filters` parameters](/cms/api/rest/parameters) can be passed as query strings.
+
+
+
+##### Create a user
+
+The `POST /api/users` endpoint creates a new user. Unlike `/api/auth/local/register`, this endpoint requires `create` permission for the Users & Permissions plugin.
+
+
+
+##### Update a user
+
+The `PUT /api/users/:id` endpoint updates an existing user by ID.
+
+
+
+##### Delete a user
+
+The `DELETE /api/users/:id` endpoint deletes a user by ID.
+
+
+
+To refresh your authentication token, send the following request:
+
+
+
To log out of all sessions, send the following request:
@@ -9356,6 +9544,12 @@ create: async ctx => {
};
```
+## Customizing routes and policies {#customizing-routes-and-policies}
+
+The Users & Permissions plugin routes and controllers can be extended and overridden through the [plugin extension system](/cms/plugins-development/plugins-extension). This is useful for adding custom policies to user endpoints, overriding controller logic, or adding new routes.
+
+For a complete guide with step-by-step instructions and code examples, see [Customizing Users & Permissions plugin routes](/cms/backend-customization/guides/customizing-users-permissions-plugin-routes).
+
# Installation
diff --git a/docusaurus/static/llms.txt b/docusaurus/static/llms.txt
index 45429e3d1d..b933b5f438 100644
--- a/docusaurus/static/llms.txt
+++ b/docusaurus/static/llms.txt
@@ -55,6 +55,7 @@
- [Upload files](https://docs.strapi.io/cms/api/rest/upload): Learn how to use the /api/upload endpoints to upload files to Strapi with the REST API.
- [Back-end customization](https://docs.strapi.io/cms/backend-customization): Strapi’s back end is a Koa-based server where requests pass through global middlewares, routes, controllers, services, and models before the Document Service returns responses.
- [Controllers](https://docs.strapi.io/cms/backend-customization/controllers): Controllers bundle actions that handle business logic for each route within Strapi’s MVC pattern. This documentation demonstrates generating controllers, extending core ones with createCoreController, and delegating heavy logic to services.
+- [Customizing Users & Permissions plugin routes](https://docs.strapi.io/cms/backend-customization/guides/customizing-users-permissions-plugin-routes): The Users & Permissions plugin exposes /users and /auth routes that can be extended or overridden using the plugin extension system. This guide shows how to add custom policies, override controllers, and add new routes to the User collection.
- [Middlewares](https://docs.strapi.io/cms/backend-customization/middlewares): Middlewares alter the request or response flow at application or API levels. This documentation distinguishes global versus route middlewares and illustrates custom implementations with generation patterns.
- [Models](https://docs.strapi.io/cms/backend-customization/models): Models define Strapi’s content structure via content-types and reusable components. This documentation walks through creating these models in the Content-type Builder or CLI and managing schema files with optional lifecycle hooks.
- [Policies](https://docs.strapi.io/cms/backend-customization/policies): Policies execute before controllers to enforce authorization or other checks on routes. Instructions in this documentation cover generating global or scoped policies and wiring them into router configs.
From bd442aa7f0d36cd1c586818c095a6d1278cab5ed Mon Sep 17 00:00:00 2001
From: Pierre Wizla
Date: Mon, 13 Apr 2026 16:30:17 +0200
Subject: [PATCH 06/26] Apply suggestion from @pwizla
---
docusaurus/docs/cms/features/users-permissions.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docusaurus/docs/cms/features/users-permissions.md b/docusaurus/docs/cms/features/users-permissions.md
index 2124281ffa..cfe0fb14fe 100644
--- a/docusaurus/docs/cms/features/users-permissions.md
+++ b/docusaurus/docs/cms/features/users-permissions.md
@@ -989,6 +989,6 @@ create: async ctx => {
## Customizing routes and policies {#customizing-routes-and-policies}
-The Users & Permissions plugin routes and controllers can be extended and overridden through the [plugin extension system](/cms/plugins-development/plugins-extension). This is useful for adding custom policies to user endpoints, overriding controller logic, or adding new routes.
+The Users & Permissions routes and controllers can be extended and overridden through the [plugin extension system](/cms/plugins-development/plugins-extension). This is useful for adding custom policies to user endpoints, overriding controller logic, or adding new routes.
For a complete guide with step-by-step instructions and code examples, see [Customizing Users & Permissions plugin routes](/cms/backend-customization/guides/customizing-users-permissions-plugin-routes).
From 2019e1fd4948edb51ceeb29157ba2549f4ecca2a Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Mon, 13 Apr 2026 16:36:58 +0200
Subject: [PATCH 07/26] Move route customization section under Code-based
configuration with doc card
---
docusaurus/docs/cms/features/users-permissions.md | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/docusaurus/docs/cms/features/users-permissions.md b/docusaurus/docs/cms/features/users-permissions.md
index 2124281ffa..ba4f6213b5 100644
--- a/docusaurus/docs/cms/features/users-permissions.md
+++ b/docusaurus/docs/cms/features/users-permissions.md
@@ -520,6 +520,14 @@ If you need to configure a custom handler to accept other URLs, you can create a
},
```
+### Customizing routes and policies {#customizing-routes-and-policies}
+
+The Users & Permissions plugin routes and controllers can be extended and overridden through the [plugin extension system](/cms/plugins-development/plugins-extension). This is useful for adding custom policies to user endpoints, overriding controller logic, or adding new routes.
+
+
+
+
+
## Usage
The Users & Permissions feature can be used both via the admin panel, to create new end-user accounts, and via the APIs.
@@ -986,9 +994,3 @@ create: async ctx => {
ctx.created(data);
};
```
-
-## Customizing routes and policies {#customizing-routes-and-policies}
-
-The Users & Permissions plugin routes and controllers can be extended and overridden through the [plugin extension system](/cms/plugins-development/plugins-extension). This is useful for adding custom policies to user endpoints, overriding controller logic, or adding new routes.
-
-For a complete guide with step-by-step instructions and code examples, see [Customizing Users & Permissions plugin routes](/cms/backend-customization/guides/customizing-users-permissions-plugin-routes).
From 93ed22f2ba1678baf1bf798ab9eeff9177cd6d59 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Mon, 13 Apr 2026 16:38:59 +0200
Subject: [PATCH 08/26] Fix parallel heading structure in Code-based
configuration section
---
docusaurus/docs/cms/features/users-permissions.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docusaurus/docs/cms/features/users-permissions.md b/docusaurus/docs/cms/features/users-permissions.md
index ba4f6213b5..d10dc500dc 100644
--- a/docusaurus/docs/cms/features/users-permissions.md
+++ b/docusaurus/docs/cms/features/users-permissions.md
@@ -409,7 +409,7 @@ export default ({ env }) => ({
-### Templating emails
+### Email template configuration {#templating-emails}
By default this plugin comes with two templates: reset password and email address confirmation. The templates use to populate the variables.
@@ -520,7 +520,7 @@ If you need to configure a custom handler to accept other URLs, you can create a
},
```
-### Customizing routes and policies {#customizing-routes-and-policies}
+### Route and policy customization {#customizing-routes-and-policies}
The Users & Permissions plugin routes and controllers can be extended and overridden through the [plugin extension system](/cms/plugins-development/plugins-extension). This is useful for adding custom policies to user endpoints, overriding controller logic, or adding new routes.
From d6a147fb0da31168c1f9de7761344d1e3f9e4da8 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Mon, 13 Apr 2026 16:46:05 +0200
Subject: [PATCH 09/26] Fix stale source annotation and ambiguous pronoun in
guide
---
.../guides/customizing-users-permissions-plugin-routes.md | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index fcb83a1d2f..7d124f1786 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -50,7 +50,7 @@ Route configs can also include optional `policies` and `middlewares` arrays (see
The available `user` controller actions are: `find`, `findOne`, `create`, `update`, `destroy`, `me`, and `count`.
-
+
The available `auth` controller actions are: `callback` (login), `register`, `forgotPassword`, `resetPassword`, `changePassword`, `emailConfirmation`, `sendEmailConfirmation`, `connect`, `refresh`, and `logout`.
:::note Understanding the controller types
@@ -91,7 +91,7 @@ export default (plugin) => {
-The function receives the full plugin object and must return it. You can modify `plugin.routes`, `plugin.controllers`, `plugin.policies`, and `plugin.services` before returning.
+The function receives the full plugin object and must return the plugin. You can modify `plugin.routes`, `plugin.controllers`, `plugin.policies`, and `plugin.services` before returning.
## Add a custom policy to a user route {#add-custom-policy}
@@ -157,7 +157,6 @@ export default (policyContext, config, { strapi }) => {
### 2. Attach the policy to the user routes
-
In the plugin extension file, find the `update` and `delete` routes and add the policy:
From 4f929cc741671fc9d0b70a05309f8b94a498fffd Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Mon, 13 Apr 2026 16:59:33 +0200
Subject: [PATCH 10/26] Restore original phrasing for route registration
comparison
---
.../guides/customizing-users-permissions-plugin-routes.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index 7d124f1786..b7626b2c27 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -14,13 +14,13 @@ tags:
# Customizing Users & Permissions plugin routes
-The Users & Permissions plugin exposes `/users` and `/auth` routes that can be extended or overridden using the plugin extension system. This guide shows how to add custom policies, override controllers, and add new routes to the User collection.
+The Users & Permissions feature exposes `/users` and `/auth` routes that can be extended or overridden using the plugin extension system. This guide shows how to add custom policies, override controllers, and add new routes to the User collection.
-The [Users & Permissions plugin](/cms/features/users-permissions) ships with built-in routes for authentication (`/auth`) and user management (`/users`). Because these routes belong to a plugin rather than a user-created content-type, they cannot be customized with `createCoreRouter`. Instead, extend them through the [plugin extension system](/cms/plugins-development/plugins-extension) using a `strapi-server.js` or `strapi-server.ts` file in the `/src/extensions/users-permissions/` folder.
+The [Users & Permissions feature](/cms/features/users-permissions) ships with built-in routes for authentication (`/auth`) and user management (`/users`). Because these routes belong to a plugin rather than a user-created content-type, they cannot be customized with `createCoreRouter`. Instead, extend them through the [plugin extension system](/cms/plugins-development/plugins-extension) using a `strapi-server.js` or `strapi-server.ts` file in the `/src/extensions/users-permissions/` folder.
:::prerequisites
-- A Strapi 5 project with the Users & Permissions plugin installed (included by default).
+- A Strapi 5 project.
- Familiarity with [routes](/cms/backend-customization/routes) and [policies](/cms/backend-customization/policies).
:::
@@ -29,7 +29,7 @@ The [Users & Permissions plugin](/cms/features/users-permissions) ships with bui
-Content-types you create (e.g., `api::restaurant.restaurant`) register routes differently. The Users & Permissions plugin registers its routes inside the `plugin.routes['content-api'].routes` array, which contains all `/users`, `/auth`, and `/roles` route definitions.
+Unlike content-types you create (e.g., `api::restaurant.restaurant`), the Users & Permissions plugin registers its routes inside the `plugin.routes['content-api'].routes` array. This array contains all `/users`, `/auth`, and `/roles` route definitions.
Each route is an object with the following shape:
From 94b7c0782b3184e0b16aa734208ce9aca8ea3284 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 11:13:40 +0200
Subject: [PATCH 11/26] Replace 'config' with 'configuration' in guide prose
---
.../guides/customizing-users-permissions-plugin-routes.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index b7626b2c27..006400a0f7 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -45,7 +45,7 @@ Each route is an object with the following shape:
}
```
-Route configs can also include optional `policies` and `middlewares` arrays (see [Add a custom policy to a user route](#add-custom-policy)).
+Route configurations can also include optional `policies` and `middlewares` arrays (see [Add a custom policy to a user route](#add-custom-policy)).
The available `user` controller actions are: `find`, `findOne`, `create`, `update`, `destroy`, `me`, and `count`.
@@ -102,7 +102,7 @@ A common requirement is restricting who can update or delete user accounts: for
-Create a global policy that checks whether the authenticated user matches the target user. The policy function receives the Koa context (with access to `state.user` and `params`), an optional config object, and `{ strapi }`:
+Create a global policy that checks whether the authenticated user matches the target user. The policy function receives the Koa context (with access to `state.user` and `params`), an optional configuration object, and `{ strapi }`:
From 4c702025dc4fc42124bb2043fa05e70d05f8bd13 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 11:42:21 +0200
Subject: [PATCH 12/26] Fix style, integrity, and coherence issues in
users-permissions page
- Remove misleading API tokens references from description and Tldr
- Fix extension path from /extensions/ to /src/extensions/ (v5 convention)
- Fix 'registered' to 'authenticated' for default role name
- Fix stale anchor #creating-a-custom-password-validation
- Fix em dash, sentence case heading, and various prose issues
---
.../docs/cms/features/users-permissions.md | 52 +++++++++----------
1 file changed, 26 insertions(+), 26 deletions(-)
diff --git a/docusaurus/docs/cms/features/users-permissions.md b/docusaurus/docs/cms/features/users-permissions.md
index 68d1e984ae..96b8fea1d0 100644
--- a/docusaurus/docs/cms/features/users-permissions.md
+++ b/docusaurus/docs/cms/features/users-permissions.md
@@ -1,19 +1,19 @@
---
title: Users & Permissions
-description: Learn to use the Users & Permissions and API tokens features to manage end-users.
+description: Learn to use the Users & Permissions feature to manage end-user accounts, authentication, and role-based access.
displayed_sidebar: cmsSidebar
toc_max_heading_level: 5
tags:
- admin panel
- users & permissions
-- api tokens
+- authentication
- features
---
# Users & Permissions
-Users & Permissions manages end-user accounts, JWT-based authentication, and role-based access to APIs. This documentation explains how to create roles, configure permissions, and issue API tokens for secure access control.
+Users & Permissions manages end-user accounts, JWT-based authentication, and role-based access to APIs. This documentation explains how to create roles, configure permissions, and secure access to your content API.
The Users & Permissions feature allows the management of the end-users 💡 **What are end users?**
End-users are the users who consume the content that is created and managed with a Strapi application and displayed on front-end applications (e.g. websites, mobile applications, connected devices etc.). Unlike the administrators, they do not have access to the admin panel. of a Strapi project. It provides a full authentication process based on JSON Web Tokens (JWT) to protect your API, and an access-control list (ACL) strategy that enables you to manage permissions between groups of users.
@@ -31,13 +31,13 @@ The Users & Permissions feature is configured from both the admin panel settings
### Roles
-The Users & Permissions feature allows to create and manage roles for end users, to configure what they can have access to.
+The Users & Permissions feature allows creating and managing roles for end users, to configure what they can have access to.
#### Creating a new role
**Path:** *Users & Permissions plugin > Roles*
-On the top right side of the *Roles* interface, an **Add new role** button is displayed. It allows to create a new role for end users of your Strapi application.
+On the top right side of the *Roles* interface, an **Add new role** button is displayed. It allows creating a new role for end users of your Strapi application.
Click on the **Add new role** button to be redirected to the roles edition interface, where you will be able to name your new role and define its details and permissions (see [Editing a role](#editing-a-role)).
@@ -66,7 +66,7 @@ By default, 2 end-user roles are defined for any Strapi application:
More roles can however be created (see [Creating a new role](#creating-a-new-role)), and all can be edited through the role edition interface.
-1. Click on the edit button of the role to edit — except if you directly landed on the role edition interface from creating a new role.
+1. Click on the edit button of the role to edit. Skip this step if you directly landed on the role edition interface from creating a new role.
2. Fill in the *Role details*, following the instructions from the table below:
| Role details | Instructions |
@@ -104,7 +104,7 @@ Although the 2 default end-user roles cannot be deleted, the other ones can, as
**Path:** *Users & Permissions plugin > Providers*
-The Users & Permissions feature allows enabling and configuring providers, for end users to login via a third-party provider to access the content of a front-end application through the Strapi application API.
+The Users & Permissions feature allows enabling and configuring providers, for end users to log in via a third-party provider to access the content of a front-end application through the Strapi application API.
By default, a list of providers is available including one, "Email", enabled by default for all Strapi applications with Users & Permissions enabled.
@@ -165,7 +165,7 @@ Both email templates can be modified.
}}
/>
-### Advanced Settings
+### Advanced settings
**Path:** *Users & Permissions plugin > Advanced settings*
@@ -210,9 +210,9 @@ Defining which mode is used is done by setting the `jwtManagement` property of t
| Mode | Description | Use case |
|------|-------------|----------|
| `legacy-support` | (default) Issues long-lived JWTs using traditional configuration | Existing applications, simple authentication |
-| `refresh` | Uses session management with short-lived access tokens and refresh tokens for enhanced security | New applications, enhanced security requirements
(additional information can be found in [admin panel configuration](/cms/configurations/admin-panel#session-management) documentation) |
+| `refresh` | Uses session management with short-lived access tokens and refresh tokens for enhanced security | New applications, enhanced security requirements
(see [admin panel configuration](/cms/configurations/admin-panel#session-management)) |
-For backwards compatibility, the Users & Permission feature defaults to legacy mode:
+For backwards compatibility, the Users & Permissions feature defaults to legacy mode:
```js title="/config/plugins.js"
module.exports = ({ env }) => ({
@@ -230,14 +230,14 @@ module.exports = ({ env }) => ({
:::note Notes
- `jwtSecret` is a random string used to create new JWTs, typically set using the `JWT_SECRET` [environment variable](/cms/configurations/environment#strapi).
- `jwt.expiresIn` is (legacy-mode only) is expressed in seconds or a string describing a time span.
- Eg: 60, "45m", "10h", "2 days", "7d", "2y". A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (minutes, hours, days, years, etc), otherwise milliseconds unit is used by default ("120" is equal to "120ms").
+ For example: 60, "45m", "10h", "2 days", "7d", "2y". A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (minutes, hours, days, years, etc), otherwise milliseconds unit is used by default ("120" is equal to "120ms").
:::
:::warning
Setting JWT expiry for more than 30 days is not recommended due to security concerns.
:::
-When the `refresh` mode is used, the configuration file could look like as follows:
+When the `refresh` mode is used, the configuration file looks like the following:
@@ -303,7 +303,7 @@ export default ({ env }) => ({
### Registration configuration
-If you have added any additional fields in your User **model** Models, also called content-types in Strapi, define a representation of the content structure.
Users are a special type of built-in content-type found in any new Strapi application. You can customize the Users model, adding more fields for instance, like any other models.
For more information, please refer to the [models](/cms/backend-customization/models) documentation. that need to be accepted on registration, you need to added them to the list of allowed fields in the `config.register` object of [the `/config/plugins` file](/cms/configurations/plugins), otherwise they will not be accepted.
+If you have added any additional fields in your User **model** Models, also called content-types in Strapi, define a representation of the content structure.
Users are a special type of built-in content-type found in any new Strapi application. You can customize the Users model, adding more fields for instance, like any other models.
For more information, please refer to the [models](/cms/backend-customization/models) documentation. that need to be accepted on registration, you need to add them to the list of allowed fields in the `config.register` object of [the `/config/plugins` file](/cms/configurations/plugins), otherwise they will not be accepted.
The following example shows how to ensure a field called "nickname" is accepted by the API on user registration:
@@ -349,7 +349,7 @@ export default ({ env }) => ({
### Rate limiting configuration
-Rate limiting is applied to authentication and registration endpoints to prevent abuse. The following parameters can be configured to change its behavior. Additional configuration options are provided by the package:
+Rate limiting is applied to authentication and registration endpoints to prevent abuse. The following parameters can be configured to change its behavior. Additional configuration options are provided by the package:
The following options are available in [the `/config/plugins` file](/cms/configurations/plugins):
@@ -426,7 +426,7 @@ The following variables can be used:
- `email`
- `TOKEN` corresponds to the token generated to be able to reset the password.
- `URL` is the link where the user will be redirected after clicking on it in the email.
-- `SERVER_URL` is the absolute server url (configured in server configuration).
+- `SERVER_URL` is the absolute server URL (configured in server configuration).
@@ -438,7 +438,7 @@ The following variables can be used:
- `email`
- `CODE` corresponds to the CODE generated to be able confirm the user email.
- `URL` is the Strapi backend URL that confirms the code (by default `/auth/email-confirmation`).
-- `SERVER_URL` is the absolute server url (configured in server configuration).
+- `SERVER_URL` is the absolute server URL (configured in server configuration).
@@ -446,7 +446,7 @@ The following variables can be used:
### Security configuration
-JWTs can be verified and trusted because the information is digitally signed. To sign a token a _secret_ is required. By default Strapi generates and stores it in `/extensions/users-permissions/config/jwt.js`.
+JWTs can be verified and trusted because the information is digitally signed. To sign a token a _secret_ is required. By default Strapi generates and stores it in `/src/extensions/users-permissions/config/jwt.js`.
This is useful during development but for security reasons it is recommended to set a custom token via an environment variable `JWT_SECRET` when deploying to production.
@@ -456,7 +456,7 @@ By default you can set a `JWT_SECRET` environment variable and it will be used a
-```js title="/extensions/users-permissions/config/jwt.js"
+```js title="/src/extensions/users-permissions/config/jwt.js"
module.exports = {
jwtSecret: process.env.SOME_ENV_VAR,
@@ -467,7 +467,7 @@ module.exports = {
-```ts title="/extensions/users-permissions/config/jwt.ts"
+```ts title="/src/extensions/users-permissions/config/jwt.ts"
export default {
jwtSecret: process.env.SOME_ENV_VAR,
@@ -478,7 +478,7 @@ export default {
-#### Creating a custom callback validator {#creating-a-custom-password-validation}
+#### Creating a custom callback validator {#creating-a-custom-callback-validator}
By default, Strapi SSO only redirects to the redirect URL that is exactly equal to the url in the configuration:
@@ -490,7 +490,7 @@ By default, Strapi SSO only redirects to the redirect URL that is exactly equal
}}
/>
-If you need to configure a custom handler to accept other URLs, you can create a callback `validate` function in your plugins.js for the `users-permissions` plugin.
+If you need to configure a custom handler to accept other URLs, you can create a callback `validate` function in your `plugins.js` for the `users-permissions` plugin.
```tsx title="/config/plugins.js|ts"
// ... other plugins configuration ...
@@ -546,7 +546,7 @@ With the Users & Permissions feature, the end users and their account informatio
}}
/>
-Registering new end users in a front-end application with the Users & Permissions plugin consists in adding a new entry to the User collection type.
+Registering new end users in a front-end application with the Users & Permissions plugin consists of adding a new entry to the User collection type.
1. Go to the User collection type in the Content Manager.
2. Click on the **Create new entry** button in the top right corner.
@@ -562,7 +562,7 @@ Registering new end users in a front-end application with the Users & Permission
4. Click on the **Save** button.
:::note
-If end users can register themselves on your front-end application (see "Enable signups" option in [advanced settings](#advanced-settings)), a new entry will automatically be created and the fields of that entry will be filled up with the information indicated by the end user. All fields can however be edited by an administrator of the Strapi application.
+If end users can register themselves on your front-end application (see "Enable signups" option in [advanced settings](#advanced-settings)), a new entry will automatically be created and the fields of that entry will be populated with the information indicated by the end user. All fields can however be edited by an administrator of the Strapi application.
:::
### API usage
@@ -597,7 +597,7 @@ When [session management](#jwt-management-modes) is enabled (`jwtManagement: 're
| `POST` | `/api/auth/refresh` | Refresh access token using refresh token |
| `POST` | `/api/auth/logout` | Revoke user sessions (supports device-specific logout) |
-To refresh your authentication token you could for instance send the following request:
+To refresh your authentication token, send the following request:
@@ -918,7 +918,7 @@ If the request is successful you will receive the **user's JWT** in the `jwt` ke
The `jwt` may then be used for making permission-restricted API requests. To make an API request as a user place the JWT into an `Authorization` header of the `GET` request.
-Any request without a token will assume the `public` role permissions by default. Modify the permissions of each user's role in the admin dashboard.
+Any request without a token will assume the `public` role permissions by default. Modify the permissions of each user's role in the admin panel.
Authentication failures return a `401 (unauthorized)` error.
@@ -948,7 +948,7 @@ axios
#### User registration
-Creating a new user in the database with a default role as 'registered' can be done like in the following example:
+Creating a new user in the database with a default role as 'authenticated' can be done as in the following example:
```js
import axios from 'axios';
From 634fb84854604d7e980a0d615f1450f8000e637b Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 11:42:35 +0200
Subject: [PATCH 13/26] Fix source annotation line range for user.find route
---
.../guides/customizing-users-permissions-plugin-routes.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index 006400a0f7..c71af14674 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -33,7 +33,7 @@ Unlike content-types you create (e.g., `api::restaurant.restaurant`), the Users
Each route is an object with the following shape:
-
+
```js
{
method: 'GET', // HTTP method
From 6211af625e3a7b0edbaf6c11d6ef879fbab907ed Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 12:02:40 +0200
Subject: [PATCH 14/26] Replace inline action lists with reference tables in
guide
Add handler-to-endpoint mapping tables for user and auth controllers,
including HTTP method, path, and rate limiting information.
---
...omizing-users-permissions-plugin-routes.md | 37 ++++++++++++++++---
1 file changed, 31 insertions(+), 6 deletions(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index c71af14674..8304c4a866 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -17,7 +17,7 @@ tags:
The Users & Permissions feature exposes `/users` and `/auth` routes that can be extended or overridden using the plugin extension system. This guide shows how to add custom policies, override controllers, and add new routes to the User collection.
-The [Users & Permissions feature](/cms/features/users-permissions) ships with built-in routes for authentication (`/auth`) and user management (`/users`). Because these routes belong to a plugin rather than a user-created content-type, they cannot be customized with `createCoreRouter`. Instead, extend them through the [plugin extension system](/cms/plugins-development/plugins-extension) using a `strapi-server.js` or `strapi-server.ts` file in the `/src/extensions/users-permissions/` folder.
+The [Users & Permissions feature](/cms/features/users-permissions) ships with built-in routes for authentication (`/auth`) and user management (`/users`). Because these routes belong to a plugin rather than a user-created content-type, they cannot be customized with `createCoreRouter`. Instead, extend them through the [plugin extension system](/cms/plugins-development/plugins-extension) using a `strapi-server` file in the `/src/extensions/users-permissions/` folder.
:::prerequisites
- A Strapi 5 project.
@@ -48,13 +48,38 @@ Each route is an object with the following shape:
Route configurations can also include optional `policies` and `middlewares` arrays (see [Add a custom policy to a user route](#add-custom-policy)).
-The available `user` controller actions are: `find`, `findOne`, `create`, `update`, `destroy`, `me`, and `count`.
+
+The `user` controller is a plain object. It exposes the following actions:
+
+| Action | Method | Path | Description |
+| ------ | ------ | ---- | ----------- |
+| `user.count` | `GET` | `/users/count` | Count users |
+| `user.find` | `GET` | `/users` | Find all users |
+| `user.me` | `GET` | `/users/me` | Get authenticated user |
+| `user.findOne` | `GET` | `/users/:id` | Find one user |
+| `user.create` | `POST` | `/users` | Create a user |
+| `user.update` | `PUT` | `/users/:id` | Update a user |
+| `user.destroy` | `DELETE` | `/users/:id` | Delete a user |
-The available `auth` controller actions are: `callback` (login), `register`, `forgotPassword`, `resetPassword`, `changePassword`, `emailConfirmation`, `sendEmailConfirmation`, `connect`, `refresh`, and `logout`.
-
-:::note Understanding the controller types
-The `user` controller is a plain object with methods, while the `auth` controller is a factory function `({ strapi }) => ({...})` that Strapi resolves lazily. In the plugin extension file, both are accessible on `plugin.controllers`, but they behave differently when overridden. See [Override a controller action](#override-controller) and [Override an auth controller action](#override-auth-route) for the correct pattern for each.
+
+The `auth` controller is a factory function `({ strapi }) => ({...})`. It exposes the following actions:
+
+| Action | Method | Path | Rate limited |
+| ------ | ------ | ---- | ------------ |
+| `auth.callback` | `POST` | `/auth/local` | Yes |
+| `auth.register` | `POST` | `/auth/local/register` | Yes |
+| `auth.connect` | `GET` | `/connect/(.*)` | Yes |
+| `auth.forgotPassword` | `POST` | `/auth/forgot-password` | Yes |
+| `auth.resetPassword` | `POST` | `/auth/reset-password` | Yes |
+| `auth.changePassword` | `POST` | `/auth/change-password` | Yes |
+| `auth.emailConfirmation` | `GET` | `/auth/email-confirmation` | No |
+| `auth.sendEmailConfirmation` | `POST` | `/auth/send-email-confirmation` | No |
+| `auth.refresh` | `POST` | `/auth/refresh` | No |
+| `auth.logout` | `POST` | `/auth/logout` | No |
+
+:::note
+Because the two controllers have different types (plain object vs. factory function), they require different override patterns. See [Override a controller action](#override-controller) and [Override an auth controller action](#override-auth-route) for the correct approach for each.
:::
## Extend routes with strapi-server {#extend-routes}
From b88ba333fd5865a5fd8b82954449214afe2c6be4 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 12:06:25 +0200
Subject: [PATCH 15/26] Add tip cross-linking is-owner middleware and policy
examples
---
.../guides/customizing-users-permissions-plugin-routes.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index 8304c4a866..d0231a4c7f 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -275,6 +275,10 @@ throw new PolicyError('You can only modify your own account');
See the [policies documentation](/cms/backend-customization/policies) for more details.
:::
+:::tip
+The `is-own-user` policy above applies specifically to Users & Permissions plugin routes. For a similar pattern on standard content-types (restricting access to the entry author), see the [is-owner middleware example](/cms/backend-customization/middlewares#restricting-content-access-with-an-is-owner-policy) and the [is-owner-review policy example](/cms/backend-customization/examples/policies#creating-a-custom-policy).
+:::
+
## Override a controller action {#override-controller}
From 40c70c64943a2c79617926f0ab30c35ab7222e15 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 13:54:15 +0200
Subject: [PATCH 16/26] Move is-owner cross-link tip to end of Create the
policy file section
---
.../guides/customizing-users-permissions-plugin-routes.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index d0231a4c7f..642c1157a8 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -179,6 +179,10 @@ export default (policyContext, config, { strapi }) => {
+:::tip
+The `is-own-user` policy above applies specifically to Users & Permissions plugin routes. For a similar pattern on standard content-types (restricting access to the entry author), see the [is-owner middleware example](/cms/backend-customization/middlewares#restricting-content-access-with-an-is-owner-policy) and the [is-owner-review policy example](/cms/backend-customization/examples/policies#creating-a-custom-policy).
+:::
+
### 2. Attach the policy to the user routes
@@ -275,10 +279,6 @@ throw new PolicyError('You can only modify your own account');
See the [policies documentation](/cms/backend-customization/policies) for more details.
:::
-:::tip
-The `is-own-user` policy above applies specifically to Users & Permissions plugin routes. For a similar pattern on standard content-types (restricting access to the entry author), see the [is-owner middleware example](/cms/backend-customization/middlewares#restricting-content-access-with-an-is-owner-policy) and the [is-owner-review policy example](/cms/backend-customization/examples/policies#creating-a-custom-policy).
-:::
-
## Override a controller action {#override-controller}
From 3382229ce73186b20ad86ebfb977b7a84cc02499 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 13:58:10 +0200
Subject: [PATCH 17/26] Rename controller override headings to specify user vs
auth
---
.../guides/customizing-users-permissions-plugin-routes.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index 642c1157a8..fc44c99ed1 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -79,7 +79,7 @@ The `auth` controller is a factory function `({ strapi }) => ({...})`. It expose
| `auth.logout` | `POST` | `/auth/logout` | No |
:::note
-Because the two controllers have different types (plain object vs. factory function), they require different override patterns. See [Override a controller action](#override-controller) and [Override an auth controller action](#override-auth-route) for the correct approach for each.
+Because the two controllers have different types (plain object vs. factory function), they require different override patterns. See [Override a `user` controller action](#override-controller) and [Override an `auth` controller action](#override-auth-route) for the correct approach for each.
:::
## Extend routes with strapi-server {#extend-routes}
@@ -279,7 +279,7 @@ throw new PolicyError('You can only modify your own account');
See the [policies documentation](/cms/backend-customization/policies) for more details.
:::
-## Override a controller action {#override-controller}
+## Override a `user` controller action {#override-controller}
@@ -337,7 +337,7 @@ export default (plugin) => {
When wrapping a controller, always call the original function first to preserve the default behavior. Skipping the original function means you take over the full request handling, including sanitization and error handling.
:::
-## Override an auth controller action {#override-auth-route}
+## Override an `auth` controller action {#override-auth-route}
@@ -676,4 +676,4 @@ After making changes, restart Strapi and verify your customizations:
| Controller returns 500 | The controller action name does not match the `handler` value in the route definition. |
| Changes not reflected | Strapi was not restarted after modifying the extension file. Extensions are loaded at startup. |
| Permission denied (403) | The new action is not enabled for the role. Enable it in *Users & Permissions plugin > Roles*. |
-| Cannot read property of `auth` controller | The `auth` controller is a factory function, not a plain object. Wrap the factory instead of accessing methods directly (see [Override an auth controller action](#override-auth-route)). |
+| Cannot read property of `auth` controller | The `auth` controller is a factory function, not a plain object. Wrap the factory instead of accessing methods directly (see [Override an `auth` controller action](#override-auth-route)). |
From a0768ecdb1ec4675ea8317da103d78b6ef6ea665 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 14:09:19 +0200
Subject: [PATCH 18/26] Restructure guide into context, routes, controllers,
and full example
Group background sections (route structure, strapi-server scaffold,
available actions) under a single "How it works" H2. Group route
operations (add policy, add route, remove route) under "Customize
routes" and controller overrides under "Override controllers". Rename
"Combine multiple customizations" to "Full example".
---
...omizing-users-permissions-plugin-routes.md | 324 +++++++++---------
1 file changed, 166 insertions(+), 158 deletions(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index fc44c99ed1..ba4479dbd4 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -24,7 +24,9 @@ The [Users & Permissions feature](/cms/features/users-permissions) ships with bu
- Familiarity with [routes](/cms/backend-customization/routes) and [policies](/cms/backend-customization/policies).
:::
-## How Users & Permissions routes work {#how-routes-work}
+## How it works {#how-routes-work}
+
+### Route structure
@@ -45,7 +47,45 @@ Each route is an object with the following shape:
}
```
-Route configurations can also include optional `policies` and `middlewares` arrays (see [Add a custom policy to a user route](#add-custom-policy)).
+Route configurations can also include optional `policies` and `middlewares` arrays (see [Add a custom policy](#add-custom-policy)).
+
+### The `strapi-server` extension file {#extend-routes}
+
+
+
+All customizations to the Users & Permissions plugin go in a single file:
+
+
+
+
+
+```js title="/src/extensions/users-permissions/strapi-server.js"
+module.exports = (plugin) => {
+ // Your customizations here
+
+ return plugin;
+};
+```
+
+
+
+
+
+```ts title="/src/extensions/users-permissions/strapi-server.ts"
+export default (plugin) => {
+ // Your customizations here
+
+ return plugin;
+};
+```
+
+
+
+
+
+The function receives the full plugin object and must return the plugin. You can modify `plugin.routes`, `plugin.controllers`, `plugin.policies`, and `plugin.services` before returning.
+
+### Available actions {#available-actions}
@@ -82,47 +122,13 @@ The `auth` controller is a factory function `({ strapi }) => ({...})`. It expose
Because the two controllers have different types (plain object vs. factory function), they require different override patterns. See [Override a `user` controller action](#override-controller) and [Override an `auth` controller action](#override-auth-route) for the correct approach for each.
:::
-## Extend routes with strapi-server {#extend-routes}
+## Customize routes {#customize-routes}
-
-
-All customizations to the Users & Permissions plugin go in a single file:
-
-
-
-
-
-```js title="/src/extensions/users-permissions/strapi-server.js"
-module.exports = (plugin) => {
- // Your customizations here
-
- return plugin;
-};
-```
-
-
-
-
-
-```ts title="/src/extensions/users-permissions/strapi-server.ts"
-export default (plugin) => {
- // Your customizations here
-
- return plugin;
-};
-```
-
-
-
-
-
-The function receives the full plugin object and must return the plugin. You can modify `plugin.routes`, `plugin.controllers`, `plugin.policies`, and `plugin.services` before returning.
-
-## Add a custom policy to a user route {#add-custom-policy}
+### Add a custom policy {#add-custom-policy}
A common requirement is restricting who can update or delete user accounts: for example, ensuring users can only update their own profile.
-### 1. Create the policy file
+#### 1. Create the policy file
@@ -183,7 +189,7 @@ export default (policyContext, config, { strapi }) => {
The `is-own-user` policy above applies specifically to Users & Permissions plugin routes. For a similar pattern on standard content-types (restricting access to the entry author), see the [is-owner middleware example](/cms/backend-customization/middlewares#restricting-content-access-with-an-is-owner-policy) and the [is-owner-review policy example](/cms/backend-customization/examples/policies#creating-a-custom-policy).
:::
-### 2. Attach the policy to the user routes
+#### 2. Attach the policy to the user routes
@@ -279,7 +285,123 @@ throw new PolicyError('You can only modify your own account');
See the [policies documentation](/cms/backend-customization/policies) for more details.
:::
-## Override a `user` controller action {#override-controller}
+### Add a new route {#add-new-route}
+
+You can add custom routes to the Users & Permissions plugin. For example, to add an endpoint that deactivates a user account:
+
+
+
+
+
+
+
+```js title="/src/extensions/users-permissions/strapi-server.js"
+module.exports = (plugin) => {
+ // Add a new controller action
+ plugin.controllers.user.deactivate = async (ctx) => {
+ const { id } = ctx.params;
+
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .edit(id, { blocked: true });
+
+ ctx.body = { message: `User ${user.username} has been deactivated` };
+ };
+
+ // Register the route
+ plugin.routes['content-api'].routes.push({
+ method: 'POST',
+ path: '/users/:id/deactivate',
+ handler: 'user.deactivate',
+ config: {
+ prefix: '',
+ policies: ['global::is-own-user'],
+ },
+ });
+
+ return plugin;
+};
+```
+
+
+
+
+
+```ts title="/src/extensions/users-permissions/strapi-server.ts"
+export default (plugin) => {
+ // Add a new controller action
+ plugin.controllers.user.deactivate = async (ctx) => {
+ const { id } = ctx.params;
+
+ const user = await strapi
+ .plugin('users-permissions')
+ .service('user')
+ .edit(id, { blocked: true });
+
+ ctx.body = { message: `User ${user.username} has been deactivated` };
+ };
+
+ // Register the route
+ plugin.routes['content-api'].routes.push({
+ method: 'POST',
+ path: '/users/:id/deactivate',
+ handler: 'user.deactivate',
+ config: {
+ prefix: '',
+ policies: ['global::is-own-user'],
+ },
+ });
+
+ return plugin;
+};
+```
+
+
+
+
+
+After restarting Strapi, `POST /api/users/:id/deactivate` becomes available. Grant the corresponding permission in the admin panel under *Users & Permissions plugin > Roles* for the roles that should access this endpoint.
+
+### Remove a route {#remove-route}
+
+You can disable a route by filtering it out of the routes array. For example, to disable the user count endpoint:
+
+
+
+
+
+```js title="/src/extensions/users-permissions/strapi-server.js"
+module.exports = (plugin) => {
+ plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter(
+ (route) => route.handler !== 'user.count'
+ );
+
+ return plugin;
+};
+```
+
+
+
+
+
+```ts title="/src/extensions/users-permissions/strapi-server.ts"
+export default (plugin) => {
+ plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter(
+ (route) => route.handler !== 'user.count'
+ );
+
+ return plugin;
+};
+```
+
+
+
+
+
+## Override controllers {#override-controllers}
+
+### Override a `user` controller action {#override-controller}
@@ -337,7 +459,7 @@ export default (plugin) => {
When wrapping a controller, always call the original function first to preserve the default behavior. Skipping the original function means you take over the full request handling, including sanitization and error handling.
:::
-## Override an `auth` controller action {#override-auth-route}
+### Override an `auth` controller action {#override-auth-route}
@@ -418,123 +540,9 @@ export default (plugin) => {
Do not access `plugin.controllers.auth.register` directly. Because `auth` is a factory function at extension time, its methods are not accessible until Strapi calls the factory. Always wrap the factory as shown above.
:::
-## Add a new route {#add-new-route}
-
-You can add custom routes to the Users & Permissions plugin. For example, to add an endpoint that deactivates a user account:
-
-
-
-
-
-
-
-```js title="/src/extensions/users-permissions/strapi-server.js"
-module.exports = (plugin) => {
- // Add a new controller action
- plugin.controllers.user.deactivate = async (ctx) => {
- const { id } = ctx.params;
-
- const user = await strapi
- .plugin('users-permissions')
- .service('user')
- .edit(id, { blocked: true });
-
- ctx.body = { message: `User ${user.username} has been deactivated` };
- };
-
- // Register the route
- plugin.routes['content-api'].routes.push({
- method: 'POST',
- path: '/users/:id/deactivate',
- handler: 'user.deactivate',
- config: {
- prefix: '',
- policies: ['global::is-own-user'],
- },
- });
-
- return plugin;
-};
-```
-
-
-
-
-
-```ts title="/src/extensions/users-permissions/strapi-server.ts"
-export default (plugin) => {
- // Add a new controller action
- plugin.controllers.user.deactivate = async (ctx) => {
- const { id } = ctx.params;
-
- const user = await strapi
- .plugin('users-permissions')
- .service('user')
- .edit(id, { blocked: true });
-
- ctx.body = { message: `User ${user.username} has been deactivated` };
- };
-
- // Register the route
- plugin.routes['content-api'].routes.push({
- method: 'POST',
- path: '/users/:id/deactivate',
- handler: 'user.deactivate',
- config: {
- prefix: '',
- policies: ['global::is-own-user'],
- },
- });
-
- return plugin;
-};
-```
-
-
-
-
-
-After restarting Strapi, `POST /api/users/:id/deactivate` becomes available. Grant the corresponding permission in the admin panel under *Users & Permissions plugin > Roles* for the roles that should access this endpoint.
-
-## Remove a route {#remove-route}
-
-You can disable a route by filtering it out of the routes array. For example, to disable the user count endpoint:
-
-
-
-
-
-```js title="/src/extensions/users-permissions/strapi-server.js"
-module.exports = (plugin) => {
- plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter(
- (route) => route.handler !== 'user.count'
- );
-
- return plugin;
-};
-```
-
-
-
-
-
-```ts title="/src/extensions/users-permissions/strapi-server.ts"
-export default (plugin) => {
- plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter(
- (route) => route.handler !== 'user.count'
- );
-
- return plugin;
-};
-```
-
-
-
-
-
-## Combine multiple customizations {#combine-customizations}
+## Full example {#combine-customizations}
-In practice, you often combine several customizations in the same file. The following example adds a policy to `update` and `delete`, wraps the `me` controller, and adds a new route:
+The following example combines several customizations in a single file: it adds a policy to `update` and `delete`, wraps the `me` controller, and adds a new route:
From a1d4ad0732df2992869a0547ffb709ee8eb1efe0 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 14:34:12 +0200
Subject: [PATCH 19/26] Add missing /permissions to route definitions list
---
.../guides/customizing-users-permissions-plugin-routes.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index ba4479dbd4..40980eb6ef 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -31,7 +31,7 @@ The [Users & Permissions feature](/cms/features/users-permissions) ships with bu
-Unlike content-types you create (e.g., `api::restaurant.restaurant`), the Users & Permissions plugin registers its routes inside the `plugin.routes['content-api'].routes` array. This array contains all `/users`, `/auth`, and `/roles` route definitions.
+Unlike content-types you create (e.g., `api::restaurant.restaurant`), the Users & Permissions plugin registers its routes inside the `plugin.routes['content-api'].routes` array. This array contains all `/users`, `/auth`, `/roles`, and `/permissions` route definitions.
Each route is an object with the following shape:
From a179dab935ad8ed7e4499fd09b3743974af7b111 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 14:34:23 +0200
Subject: [PATCH 20/26] Add missing GET /auth/:provider/callback route to auth
table
---
.../guides/customizing-users-permissions-plugin-routes.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index 40980eb6ef..8df85418b8 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -108,6 +108,7 @@ The `auth` controller is a factory function `({ strapi }) => ({...})`. It expose
| Action | Method | Path | Rate limited |
| ------ | ------ | ---- | ------------ |
| `auth.callback` | `POST` | `/auth/local` | Yes |
+| `auth.callback` | `GET` | `/auth/:provider/callback` | No |
| `auth.register` | `POST` | `/auth/local/register` | Yes |
| `auth.connect` | `GET` | `/connect/(.*)` | Yes |
| `auth.forgotPassword` | `POST` | `/auth/forgot-password` | Yes |
From bbf44e44e5ab532c7b98bc15714a4cb10ef811a4 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 14:56:25 +0200
Subject: [PATCH 21/26] Integrate standalone cross-reference into sentence flow
---
.../guides/customizing-users-permissions-plugin-routes.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index 8df85418b8..4143ad6fbd 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -120,7 +120,7 @@ The `auth` controller is a factory function `({ strapi }) => ({...})`. It expose
| `auth.logout` | `POST` | `/auth/logout` | No |
:::note
-Because the two controllers have different types (plain object vs. factory function), they require different override patterns. See [Override a `user` controller action](#override-controller) and [Override an `auth` controller action](#override-auth-route) for the correct approach for each.
+Because the two controllers have different types (plain object vs. factory function), they require different override patterns (see [Override a `user` controller action](#override-controller) and [Override an `auth` controller action](#override-auth-route)).
:::
## Customize routes {#customize-routes}
From b778d17e6d722397feb530a51df5735a0a8f7d19 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 14:56:35 +0200
Subject: [PATCH 22/26] Integrate policies cross-reference into sentence flow
---
.../guides/customizing-users-permissions-plugin-routes.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index 4143ad6fbd..72082cf81a 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -283,7 +283,7 @@ const { PolicyError } = errors;
throw new PolicyError('You can only modify your own account');
```
-See the [policies documentation](/cms/backend-customization/policies) for more details.
+For more details on policy patterns and error handling, see the [policies documentation](/cms/backend-customization/policies).
:::
### Add a new route {#add-new-route}
From ad42e48ae62f5e60f2a3a536944a07c2a992c15f Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 14:56:47 +0200
Subject: [PATCH 23/26] Specify profile route name in Full example introduction
---
.../guides/customizing-users-permissions-plugin-routes.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index 72082cf81a..fb86cba39e 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -543,7 +543,7 @@ Do not access `plugin.controllers.auth.register` directly. Because `auth` is a f
## Full example {#combine-customizations}
-The following example combines several customizations in a single file: it adds a policy to `update` and `delete`, wraps the `me` controller, and adds a new route:
+The following example combines several customizations in a single file: it adds a policy to `update` and `delete`, wraps the `me` controller, and adds a new `profile` route:
From d528cc86bcf09bc447d942377c9b91a4fd439283 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 15:32:05 +0200
Subject: [PATCH 24/26] Add introduction sentences to H2 sections lacking intro
text
---
.../guides/customizing-users-permissions-plugin-routes.md | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index fb86cba39e..f81869f732 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -24,7 +24,9 @@ The [Users & Permissions feature](/cms/features/users-permissions) ships with bu
- Familiarity with [routes](/cms/backend-customization/routes) and [policies](/cms/backend-customization/policies).
:::
-## How it works {#how-routes-work}
+## How it works
+
+The Users & Permissions plugin uses a route array and controller objects that differ from standard content-types. Understanding their structure is essential before customizing them.
### Route structure
@@ -125,6 +127,8 @@ Because the two controllers have different types (plain object vs. factory funct
## Customize routes {#customize-routes}
+You can add policies, register new endpoints, or remove existing ones by modifying the `plugin.routes['content-api'].routes` array in the extension file.
+
### Add a custom policy {#add-custom-policy}
A common requirement is restricting who can update or delete user accounts: for example, ensuring users can only update their own profile.
@@ -402,6 +406,8 @@ export default (plugin) => {
## Override controllers {#override-controllers}
+Beyond route-level customizations, you can override the controller actions themselves to change how the plugin handles requests. The `user` and `auth` controllers use different patterns, so each requires a specific approach.
+
### Override a `user` controller action {#override-controller}
From 6d1d3950a03117427c7b4c2578129f7d0eac1ccc Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 15:54:58 +0200
Subject: [PATCH 25/26] Handle minor links and style fixes
---
...omizing-users-permissions-plugin-routes.md | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
index f81869f732..402f8a8ef1 100644
--- a/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
+++ b/docusaurus/docs/cms/backend-customization/guides/customizing-users-permissions-plugin-routes.md
@@ -14,10 +14,10 @@ tags:
# Customizing Users & Permissions plugin routes
-The Users & Permissions feature exposes `/users` and `/auth` routes that can be extended or overridden using the plugin extension system. This guide shows how to add custom policies, override controllers, and add new routes to the User collection.
+[Users & Permissions](/cms/features/users-permissions) feature exposes `/users` and `/auth` routes that can be extended or overridden using the plugin extension system. This guide shows how to add custom policies, override controllers, and add new routes to the User collection.
-The [Users & Permissions feature](/cms/features/users-permissions) ships with built-in routes for authentication (`/auth`) and user management (`/users`). Because these routes belong to a plugin rather than a user-created content-type, they cannot be customized with `createCoreRouter`. Instead, extend them through the [plugin extension system](/cms/plugins-development/plugins-extension) using a `strapi-server` file in the `/src/extensions/users-permissions/` folder.
+The Users & Permissions feature ships with built-in routes for authentication (`/auth`) and user management (`/users`). Because these routes belong to a plugin rather than a user-created content-type, they cannot be customized with `createCoreRouter`. Instead, extend them through the [plugin extension system](/cms/plugins-development/plugins-extension) using a `strapi-server` file in the `/src/extensions/users-permissions/` folder.
:::prerequisites
- A Strapi 5 project.
@@ -26,7 +26,7 @@ The [Users & Permissions feature](/cms/features/users-permissions) ships with bu
## How it works
-The Users & Permissions plugin uses a route array and controller objects that differ from standard content-types. Understanding their structure is essential before customizing them.
+[Users & Permissions](/cms/features/users-permissions) uses a route array and controller objects that differ from standard content-types. Understanding their structure is essential before customizing them.
### Route structure
@@ -91,7 +91,7 @@ The function receives the full plugin object and must return the plugin. You can
-The `user` controller is a plain object. It exposes the following actions:
+The `user` controller is a plain object that exposes the following actions:
| Action | Method | Path | Description |
| ------ | ------ | ---- | ----------- |
@@ -105,7 +105,7 @@ The `user` controller is a plain object. It exposes the following actions:
-The `auth` controller is a factory function `({ strapi }) => ({...})`. It exposes the following actions:
+The `auth` controller is a factory function `({ strapi }) => ({...})` that exposes the following actions:
| Action | Method | Path | Rate limited |
| ------ | ------ | ---- | ------------ |
@@ -122,7 +122,7 @@ The `auth` controller is a factory function `({ strapi }) => ({...})`. It expose
| `auth.logout` | `POST` | `/auth/logout` | No |
:::note
-Because the two controllers have different types (plain object vs. factory function), they require different override patterns (see [Override a `user` controller action](#override-controller) and [Override an `auth` controller action](#override-auth-route)).
+Because the `user` and `auth` controllers have different types (plain object vs. factory function), they require different override patterns (see [Override a `user` controller action](#override-controller) and [Override an `auth` controller action](#override-auth-route)).
:::
## Customize routes {#customize-routes}
@@ -287,12 +287,13 @@ const { PolicyError } = errors;
throw new PolicyError('You can only modify your own account');
```
+
For more details on policy patterns and error handling, see the [policies documentation](/cms/backend-customization/policies).
:::
### Add a new route {#add-new-route}
-You can add custom routes to the Users & Permissions plugin. For example, to add an endpoint that deactivates a user account:
+You can add custom routes to the Users & Permissions plugin. For example, add an endpoint that deactivates a user account as follows:
@@ -370,7 +371,7 @@ After restarting Strapi, `POST /api/users/:id/deactivate` becomes available. Gra
### Remove a route {#remove-route}
-You can disable a route by filtering it out of the routes array. For example, to disable the user count endpoint:
+You can disable a route by filtering it out of the routes array. For example, disable the user count endpoint as follows:
@@ -549,7 +550,7 @@ Do not access `plugin.controllers.auth.register` directly. Because `auth` is a f
## Full example {#combine-customizations}
-The following example combines several customizations in a single file: it adds a policy to `update` and `delete`, wraps the `me` controller, and adds a new `profile` route:
+The following example combines several customizations in a single file: it adds a policy to `update` and `delete`, wraps the `me` controller, and adds a new `profile` route.
From 073f46a0d44fb3e9f3bee4320f6ccfe55d9c7f43 Mon Sep 17 00:00:00 2001
From: Pierre Wizla <4233866+pwizla@users.noreply.github.com>
Date: Tue, 14 Apr 2026 15:58:11 +0200
Subject: [PATCH 26/26] Restore original anchor to avoid broken external links
---
docusaurus/docs/cms/features/users-permissions.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docusaurus/docs/cms/features/users-permissions.md b/docusaurus/docs/cms/features/users-permissions.md
index 96b8fea1d0..d1d69484c8 100644
--- a/docusaurus/docs/cms/features/users-permissions.md
+++ b/docusaurus/docs/cms/features/users-permissions.md
@@ -478,7 +478,7 @@ export default {
-#### Creating a custom callback validator {#creating-a-custom-callback-validator}
+#### Creating a custom callback validator {#creating-a-custom-password-validation}
By default, Strapi SSO only redirects to the redirect URL that is exactly equal to the url in the configuration: