From 689b7e3b2b1bea1bd4f928df28bee9687db9dd6a Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Thu, 26 Feb 2026 11:01:31 +0100 Subject: [PATCH] Add `strictParams` and custom Content API parameters (#2974) * feat: addParams and strictParams * Improve based on style guide and formatting conventions * rename addBodyParams to addInputParams --------- Co-authored-by: Pierre Wizla <4233866+pwizla@users.noreply.github.com> --- docusaurus/docs/cms/api/document-service.md | 4 + .../cms/backend-customization/controllers.md | 3 +- .../docs/cms/backend-customization/routes.md | 76 +++++++++++++++++++ docusaurus/docs/cms/configurations/api.md | 20 ++++- 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service.md b/docusaurus/docs/cms/api/document-service.md index e7bb5270a0..bc6d3ed1ca 100644 --- a/docusaurus/docs/cms/api/document-service.md +++ b/docusaurus/docs/cms/api/document-service.md @@ -45,6 +45,10 @@ Additional information on how to migrate from the Entity Service API to the Docu Relations can also be connected, disconnected, and set through the Document Service API, just like with the REST API (see the [REST API relations documentation](/cms/api/rest/relations) for examples). ::: +## Configuration + +The `documents.strictParams` option enables strict validation of parameters passed to Document Service methods such as `findMany` and `findOne`. Configure it in the [API configuration](/cms/configurations/api) file (`./config/api.js` or `./config/api.ts`). See the [API configuration](/cms/configurations/api) table for details on `documents.strictParams`. + ## Document objects Document methods return a document object or a list of document objects, which represent a version of a content entry grouped under a stable `documentId`. Returned objects typically include: diff --git a/docusaurus/docs/cms/backend-customization/controllers.md b/docusaurus/docs/cms/backend-customization/controllers.md index 0cc69e6cf4..d94478a1c6 100644 --- a/docusaurus/docs/cms/backend-customization/controllers.md +++ b/docusaurus/docs/cms/backend-customization/controllers.md @@ -337,8 +337,7 @@ export default factories.createCoreController('api::restaurant.restaurant', ({ s #### Sanitization and validation when building custom controllers {#sanitize-validate-custom-controllers} -Within custom controllers, Strapi exposes the following functions via `strapi.contentAPI` for sanitization and validation: - +Within custom controllers, Strapi exposes the following functions via `strapi.contentAPI` for sanitization and validation. To add custom query or body parameters to Content API routes (e.g. in `register`), see [Custom Content API parameters](/cms/backend-customization/routes#custom-content-api-parameters). | Function Name | Parameters | Description | |------------------------------|--------------------|---------------------------------------------------------| diff --git a/docusaurus/docs/cms/backend-customization/routes.md b/docusaurus/docs/cms/backend-customization/routes.md index f5920ea584..514df90f9b 100644 --- a/docusaurus/docs/cms/backend-customization/routes.md +++ b/docusaurus/docs/cms/backend-customization/routes.md @@ -638,3 +638,79 @@ export default { + +## Custom Content API parameters {#custom-content-api-parameters} + +You can extend the `query` and body parameters allowed on Content API routes by registering them in the [register](/cms/configurations/functions#register) lifecycle. Registered parameters are then validated and sanitized like core parameters. Clients can send extra query keys (e.g. `?search=...`) or root-level body keys (e.g. `clientMutationId`) without requiring custom routes or controllers. + +| What | Where | +|------|--------| +| Enable strict parameters (reject unknown query/body keys) | [API configuration](/cms/configurations/api): set `rest.strictParams: true` in `./config/api.js` (or `./config/api.ts`). | +| Add allowed parameters (app) | Call `addQueryParams` / `addInputParams` in [register](/cms/configurations/functions#register) in `./src/index.js` or `./src/index.ts`. | +| Add allowed parameters (plugin) | Call `addQueryParams` / `addInputParams` in the plugin's [register](/cms/plugins-development/server-api#register) lifecycle. | + +When `rest.strictParams` is enabled, only core parameters and parameters on each route's request schema are accepted; the parameters you register are merged into that schema. Use the `z` instance from `@strapi/utils` (or `zod/v4`) for schemas. + +### `addQueryParams` + +`strapi.contentAPI.addQueryParams(options)` registers extra `query` parameters. Schemas must be scalar or array-of-scalars (string, number, boolean, enum). For nested structures, use `addInputParams` instead. Each entry can have an optional `matchRoute: (route) => boolean` callback to add the parameter only to routes for which the callback returns true. You cannot register core query param names (e.g. `filters`, `sort`, `fields`) as extra params; they are reserved. + +### `addInputParams` + +`strapi.contentAPI.addInputParams(options)` registers extra input parameters: root-level keys in the request body (e.g. alongside `data`), with any Zod type. The optional `matchRoute` callback works the same way as for `addQueryParams`. You cannot register reserved names such as `id` or `documentId` as input params. + +### `matchRoute` + +The `matchRoute` callback receives a `route` object with the following properties: + +- `route.method`: the HTTP method (`'GET'`, `'POST'`, etc.) +- `route.path`: the route path +- `route.handler`: the controller action string +- `route.info`: metadata about the route + +For example, to target only GET routes, use `matchRoute: (route) => route.method === 'GET'`. To target only routes whose path includes `articles`, use `matchRoute: (route) => route.path.includes('articles')`. + + + + +```js title="./src/index.js" +module.exports = { + register({ strapi }) { + strapi.contentAPI.addQueryParams({ + search: { + schema: (z) => z.string().max(200).optional(), + matchRoute: (route) => route.path.includes('articles'), + }, + }); + strapi.contentAPI.addInputParams({ + clientMutationId: { + schema: (z) => z.string().max(100).optional(), + }, + }); + }, +}; +``` + + + + +```ts title="./src/index.ts" +export default { + register({ strapi }) { + strapi.contentAPI.addQueryParams({ + search: { + schema: (z) => z.string().max(200).optional(), + matchRoute: (route) => route.path.includes('articles'), + }, + }); + strapi.contentAPI.addInputParams({ + clientMutationId: { + schema: (z) => z.string().max(100).optional(), + }, + }); + }, +}; +``` + + + \ No newline at end of file diff --git a/docusaurus/docs/cms/configurations/api.md b/docusaurus/docs/cms/configurations/api.md index 0c5142ee7e..ad00480607 100644 --- a/docusaurus/docs/cms/configurations/api.md +++ b/docusaurus/docs/cms/configurations/api.md @@ -11,10 +11,10 @@ tags: # API configuration -`/config/api` centralizes response privacy and REST defaults such as prefix, pagination limits, and max request size. +`/config/api` centralizes response privacy, REST defaults (prefix, pagination limits, max request size), and strict parameter validation for both the REST Content API and the Document Service. -General settings for API calls can be set in the `./config/api.js` file: +General settings for API calls can be set in the `./config/api.js` (or `./config/api.ts`) file. Both `rest` and `documents` options live in this single config file. | Property | Description | Type | Default | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------- | @@ -24,11 +24,17 @@ General settings for API calls can be set in the `./config/api.js` file: | `rest.prefix` | The API prefix | String | `/api` | | `rest.defaultLimit` | Default `limit` parameter used in API calls (see [REST API documentation](/cms/api/rest/sort-pagination#pagination-by-offset)) | Integer | `25` | | `rest.maxLimit` | Maximum allowed number that can be requested as `limit` (see [REST API documentation](/cms/api/rest/sort-pagination#pagination-by-offset)). | Integer | `100` | +| `rest.strictParams` | When `true`, only allowed query and body parameters are accepted on Content API routes; unknown top-level keys are rejected. Add allowed parameters via [Custom Content API parameters](/cms/backend-customization/routes#custom-content-api-parameters) in `register`. | Boolean | - | +| `documents` | Document Service configuration | Object | - | +| `documents.strictParams` | When `true`, Document Service methods reject parameters with unrecognized root-level keys (e.g., invalid `status`, `locale`). When `false` or unset, unknown parameters are ignored. See [Document Service API](/cms/api/document-service#configuration). | Boolean | - | :::note If the `rest.maxLimit` value is less than the `rest.defaultLimit` value, `maxLimit` will be the limit used. ::: +:::tip +`rest.strictParams` applies to incoming REST Content API requests (query and body). `documents.strictParams` applies to parameters passed to `strapi.documents()` in server-side code. You can enable one or both in the same config file. +::: **Example:** @@ -46,6 +52,10 @@ module.exports = ({ env }) => ({ prefix: '/v1', defaultLimit: 100, maxLimit: 250, + strictParams: true, // only allow parameters defined on routes or added via contentAPI.addQueryParams/addInputParams + }, + documents: { + strictParams: true, // reject unrecognized root-level parameters in strapi.documents() calls }, }); ``` @@ -64,10 +74,14 @@ export default ({ env }) => ({ prefix: '/v1', defaultLimit: 100, maxLimit: 250, + strictParams: true, // only allow parameters defined on routes or added via contentAPI.addQueryParams/addInputParams + }, + documents: { + strictParams: true, // reject unrecognized root-level parameters in strapi.documents() calls }, }); ``` - + \ No newline at end of file