From 5dddb9d561add4dbf3956afc7d33245888e01b1b Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Thu, 18 Jun 2026 14:43:37 +0200 Subject: [PATCH 01/75] Document publicationFilter for REST, GraphQL, and Document Service API. Adds dedicated reference pages for derived Draft & Publish cohorts, cross-links from status/parameters/draft-and-publish, and updates outdated count() guidance. --- docusaurus/docs/cms/api/document-service.md | 6 +- .../document-service/publication-filter.md | 245 +++++++++++++ .../docs/cms/api/document-service/status.md | 4 +- docusaurus/docs/cms/api/graphql.md | 56 +++ docusaurus/docs/cms/api/rest/parameters.md | 3 +- .../docs/cms/api/rest/publication-filter.md | 205 +++++++++++ docusaurus/docs/cms/api/rest/status.md | 2 + .../docs/cms/features/draft-and-publish.md | 5 +- docusaurus/sidebars.js | 2 + docusaurus/static/llms-code.txt | 295 ++++++++++++++++ docusaurus/static/llms-full.txt | 328 +++++++++++++++++- docusaurus/static/llms.txt | 2 + 12 files changed, 1147 insertions(+), 6 deletions(-) create mode 100644 docusaurus/docs/cms/api/document-service/publication-filter.md create mode 100644 docusaurus/docs/cms/api/rest/publication-filter.md diff --git a/docusaurus/docs/cms/api/document-service.md b/docusaurus/docs/cms/api/document-service.md index 75caa83314..cb0da597d3 100644 --- a/docusaurus/docs/cms/api/document-service.md +++ b/docusaurus/docs/cms/api/document-service.md @@ -101,6 +101,7 @@ Syntax: `findOne(parameters: Params) => Document` | `documentId` | Document id | | `ID` | | [`locale`](/cms/api/document-service/locale#find-one)| Locale of the document to find. | Default locale | String or `undefined` | | [`status`](/cms/api/document-service/status#find-one) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Publication status, can be: | `'draft'` | `'published'` or `'draft'` | +| [`publicationFilter`](/cms/api/document-service/publication-filter) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Derived publication cohort to match before applying `status` | - | String | | [`fields`](/cms/api/document-service/fields#findone) | [Select fields](/cms/api/document-service/fields#findone) to return | All fields
(except those not populated by default) | Object | | [`populate`](/cms/api/document-service/populate) | [Populate](/cms/api/document-service/populate) results with additional fields. | `null` | Object | @@ -150,6 +151,7 @@ Syntax: `findFirst(parameters: Params) => Document` |-----------|-------------|---------|------| | [`locale`](/cms/api/document-service/locale#find-first) | Locale of the documents to find. | Default locale | String or `undefined` | | [`status`](/cms/api/document-service/status#find-first) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Publication status, can be: | `'draft'` | `'published'` or `'draft'` | +| [`publicationFilter`](/cms/api/document-service/publication-filter) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Derived publication cohort to match before applying `status` | - | String | | [`filters`](/cms/api/document-service/filters) | [Filters](/cms/api/document-service/filters) to use | `null` | Object | | [`fields`](/cms/api/document-service/fields#findfirst) | [Select fields](/cms/api/document-service/fields#findfirst) to return | All fields
(except those not populate by default) | Object | | [`populate`](/cms/api/document-service/populate) | [Populate](/cms/api/document-service/populate) results with additional fields. | `null` | Object | @@ -240,6 +242,7 @@ Syntax: `findMany(parameters: Params) => Document[]` |-----------|-------------|---------|------| | [`locale`](/cms/api/document-service/locale#find-many) | Locale of the documents to find. | Default locale | String or `undefined` | | [`status`](/cms/api/document-service/status#find-many) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Publication status, can be: | `'draft'` | `'published'` or `'draft'` | +| [`publicationFilter`](/cms/api/document-service/publication-filter) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Derived publication cohort to match before applying `status` | - | String | | [`filters`](/cms/api/document-service/filters) | [Filters](/cms/api/document-service/filters) to use | `null` | Object | | [`fields`](/cms/api/document-service/fields#findmany) | [Select fields](/cms/api/document-service/fields#findmany) to return | All fields
(except those not populate by default) | Object | | [`populate`](/cms/api/document-service/populate) | [Populate](/cms/api/document-service/populate) results with additional fields. | `null` | Object | @@ -793,12 +796,13 @@ Syntax: `count(parameters: Params) => number` |-----------|-------------|---------|------| | [`locale`](/cms/api/document-service/locale#count) | Locale of the documents to count | Default locale | String or `null` | | [`status`](/cms/api/document-service/status#count) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Publication status, can be: | `'draft'` | `'published'` or `'draft'` | +| [`publicationFilter`](/cms/api/document-service/publication-filter) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Derived publication cohort to match before applying `status` | - | String | | [`filters`](/cms/api/document-service/filters) | [Filters](/cms/api/document-service/filters) to use | `null` | Object | :::note Since published documents necessarily also have a draft counterpart, a published document is still counted as having a draft version. -This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. There currently is no way to prevent already published documents from being counted. +This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. To count only never-published drafts, pass a [`publicationFilter`](/cms/api/document-service/publication-filter) value such as `'never-published'` or `'never-published-document'`. ::: #### Examples diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md new file mode 100644 index 0000000000..ef11cb1ecd --- /dev/null +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -0,0 +1,245 @@ +--- +title: Using publicationFilter with the Document Service API +description: Use the publicationFilter parameter with Strapi's Document Service API to query derived Draft & Publish cohorts such as never-published or modified documents. +displayed_sidebar: cmsSidebar +sidebar_label: Publication filter +tags: +- API +- Content API +- count() +- Document Service API +- Draft & Publish +- findMany() +- findFirst() +- findOne() +- publicationFilter +- status +--- + +# Document Service API: `publicationFilter` + +The [`status`](/cms/api/document-service/status) parameter selects which **row slice** to read for each document: `draft` rows have `publishedAt: null`, and `published` rows have a non-null `publishedAt`. + +The optional `publicationFilter` parameter selects a **derived publication cohort** first: a set of `(documentId, locale)` pairs (or `documentId` only when [Internationalization (i18n)](/cms/features/internationalization) is disabled) defined by how draft and published rows relate. Strapi then returns the row that matches both the cohort and the resolved `status`. + +:::prerequisites +The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. +::: + +`publicationFilter` is supported on `findOne()`, `findFirst()`, `findMany()`, and `count()`. It can be combined with [`filters`](/cms/api/document-service/filters), [`populate`](/cms/api/document-service/populate), and other query parameters. Invalid values raise a validation error. + +## Default `status` when `publicationFilter` is used {#default-status} + +`publicationFilter` is applied **after** `status` is resolved (explicitly or by default). Defaults differ by API surface: + +| API surface | Default `status` when omitted | +| ----------- | ----------------------------- | +| Document Service API (direct) | `'draft'` | +| [REST API](/cms/api/rest/publication-filter) | `'published'` | +| [GraphQL API](/cms/api/graphql#publication-filter) | `PUBLISHED` | + +Example with `publicationFilter: 'modified'` and no `status`: + +```js +// Document Service API → draft rows in the modified cohort +await strapi.documents('api::restaurant.restaurant').findMany({ + publicationFilter: 'modified', +}); + +// REST: GET /api/restaurants?publicationFilter=modified → published rows in the modified cohort +``` + +Pair-scoped modes such as `never-published` only include draft rows in the cohort. With REST or GraphQL defaults (`status=published`), those queries return an empty result set unless you pass `status=draft` / `status: DRAFT`. + +## Available values {#values} + +REST and the Document Service API use kebab-case strings. GraphQL exposes the same cohorts through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). + +| Value | Scope | Cohort definition (which `(documentId, locale)` pairs match) | +| ----- | ----- | -------------------------------------------------------------- | +| `never-published` | Pair | No row with non-null `publishedAt` exists for the same `(documentId, locale)` | +| `has-published-version` | Pair | **Both** a draft row and a published row exist for the same `(documentId, locale)` | +| `modified` | Pair | Both slices exist and `draft.updatedAt > published.updatedAt` | +| `unmodified` | Pair | Both slices exist and `draft.updatedAt <= published.updatedAt` | +| `never-published-document` | Document | No row with non-null `publishedAt` exists for the same `documentId` in **any** locale | +| `has-published-version-document` | Document | At least one published row exists for the same `documentId` in **any** locale | +| `published-without-draft` | Pair | A published row exists for the pair and **no** draft row exists for the same `(documentId, locale)` | +| `published-with-draft` | Pair | A published row exists for the pair and a draft row **also** exists for the same `(documentId, locale)` | + +For content-types without i18n, read `(documentId, locale)` as `documentId` only. + +### Semantics notes {#semantics} + +- **`has-published-version` excludes orphan published rows**: If only a published row exists for a pair (no draft sibling), that pair is **not** in the `has-published-version` cohort. Orphan published rows can appear under `published-without-draft` when querying with `status: 'published'`. +- **`modified` / `unmodified` require both slices**: Pairs with only a draft or only a published row are not included. +- **`modified` ∪ `unmodified` = `has-published-version`** (for the same `status`): The two modes partition pairs that have both slices. +- **Document-scoped modes**: Existence checks use `documentId` only. A document with draft EN + published NL qualifies for `has-published-version-document` even though EN is never published at the pair level. +- **Published-slice diagnostics** (`published-without-draft`, `published-with-draft`): Only select published rows. They are degenerate (empty) with `status: 'draft'`. + +### Content Manager list filters {#content-manager} + +The Content Manager **Status** filter (`__status`) is translated server-side. Only the **Draft (never published)** option uses `publicationFilter`: + +| Content Manager filter | Document Service query equivalent | +| ---------------------- | --------------------------------- | +| Draft (never published) | `status: 'draft'`, `publicationFilter: 'never-published-document'` | +| Published (all) | `status: 'published'` (no `publicationFilter`) | +| Published (modified) | Internal `publicationStatusFilter` (not a public REST/GraphQL parameter); similar intent to `status: 'published'` + `publicationFilter: 'modified'` but implemented separately in the Content Manager API | +| Published (unmodified) | Internal `publicationStatusFilter` (not a public REST/GraphQL parameter) | + +The **Draft (never published)** filter is document-scoped (`never-published-document`), not pair-scoped `never-published`. + +## Combine `status` and `publicationFilter` {#status-combination} + +| `status` | `publicationFilter` | Rows returned | +| -------- | ------------------- | ------------- | +| `draft` | `never-published` | Draft rows for pairs never published in that locale | +| `published` | `never-published` | Empty (degenerate) | +| `draft` | `has-published-version` | Draft rows for pairs that also have a published version | +| `published` | `has-published-version` | Published rows for pairs that also have a draft version (excludes orphan published-only pairs) | +| `draft` | `modified` | Draft rows newer than their published peer | +| `published` | `modified` | Published rows whose draft peer is newer | +| `draft` | `unmodified` | Draft rows not newer than their published peer | +| `published` | `unmodified` | Published rows whose draft peer is not newer | +| `draft` | `never-published-document` | Draft rows whose document has no published row in any locale | +| `published` | `never-published-document` | Empty (degenerate) | +| `draft` | `has-published-version-document` | Draft rows whose document has at least one published row (any locale) | +| `published` | `has-published-version-document` | Published rows whose document has at least one draft row (any locale) | +| `published` | `published-without-draft` | Published rows with no draft sibling for the same pair | +| `draft` | `published-without-draft` | Empty (degenerate) | +| `published` | `published-with-draft` | Published rows that have a draft sibling for the same pair | +| `draft` | `published-with-draft` | Empty (degenerate) | + +Valid but empty combinations do not return validation errors. + +## Query never-published drafts {#never-published} + +```js +const documents = await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'never-published', +}); +``` + +Returns draft rows for `(documentId, locale)` pairs with no published version for that locale. + +## Query has-published-version drafts {#has-published-version} + +```js +const documents = await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'has-published-version', +}); +``` + +Returns draft rows where a published row also exists for the same `(documentId, locale)`. Does not return draft rows for pairs that only exist as orphan published rows. + +## Query modified or unmodified documents {#modified-unmodified} + +```js +// Draft side of modified pairs +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'modified', +}); + +// Published side of unmodified pairs +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'published', + publicationFilter: 'unmodified', +}); +``` + +Comparison uses `updatedAt` on the draft and published rows for the same pair. + +## Query document-scoped cohorts {#document-scoped} + +```js +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'never-published-document', +}); +``` + +Returns draft rows for documents that have **never** been published in any locale. A multi-locale document with one published locale is excluded entirely, including its draft-only locales. + +```js +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'has-published-version-document', +}); +``` + +Returns draft rows for documents that have at least one published row in any locale (broader than pair-scoped `has-published-version`). + +## Query published rows without or with a draft peer {#published-slice} + +```js +// Orphan published rows (published row, no draft sibling for the same pair) +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'published', + publicationFilter: 'published-without-draft', +}); + +// Published rows that still have a draft sibling +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'published', + publicationFilter: 'published-with-draft', +}); +``` + +`published-without-draft` and `published-with-draft` partition published rows per `(documentId, locale)` (excluding pairs with no published row). + +## Use with `findOne()` and `findFirst()` {#find-one-find-first} + +`publicationFilter` applies the same cohort rules. If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists. + +```js +await strapi.documents('api::restaurant.restaurant').findOne({ + documentId: 'a1b2c3d4e5f6g7h8i9j0klm', + status: 'draft', + publicationFilter: 'never-published', +}); +``` + +## Combine with `filters` and `populate` {#filters-populate} + +`publicationFilter` is merged with other query filters (logical AND). When [populating relations](/cms/api/document-service/populate), nested queries on draft & publish content-types inherit the same cohort logic so populated results stay consistent with the parent query. + +## Count documents in a cohort {#count} + +```js +const neverPublishedCount = await strapi + .documents('api::restaurant.restaurant') + .count({ + status: 'draft', + publicationFilter: 'never-published', + }); +``` + +Without `publicationFilter`, `count({ status: 'draft' })` still counts every draft row, including drafts whose document already has a published version. Use `publicationFilter: 'never-published'` or `'never-published-document'` to count only never-published cohorts (see [`status` documentation](/cms/api/document-service/status#count)). + +## Validation {#validation} + +Unknown `publicationFilter` values are rejected: + +- Document Service API: throws a validation error. +- REST API: returns HTTP `400`. +- GraphQL: invalid enum values fail at query validation. + +## Deprecated `hasPublishedVersion` parameter {#has-published-version-deprecated} + +The boolean `hasPublishedVersion` parameter is deprecated in favor of `publicationFilter`. Strapi still accepts it on the REST API, GraphQL, and Document Service API and maps it to **document-scoped** modes: + +| `hasPublishedVersion` | Maps to | +| --------------------- | ------- | +| `false` (or string `'false'`) | `never-published-document` | +| `true` (or string `'true'`) | `has-published-version-document` | + +If both `publicationFilter` and `hasPublishedVersion` are passed, `publicationFilter` takes precedence. + +REST and GraphQL examples: [REST API: `publicationFilter`](/cms/api/rest/publication-filter#has-published-version-deprecated), [GraphQL API: `publicationFilter`](/cms/api/graphql#publication-filter). + +## Why not filter on `publishedAt` alone? {#why-not-published-at} + +A single row's `publishedAt` only describes that row. Cohorts such as `never-published`, `has-published-version`, and `modified` require comparing or correlating **two rows** for the same `(documentId, locale)`. `publicationFilter` encodes those rules in one server-side query instead of multiple client round-trips. diff --git a/docusaurus/docs/cms/api/document-service/status.md b/docusaurus/docs/cms/api/document-service/status.md index efb7042581..4a458e61e4 100644 --- a/docusaurus/docs/cms/api/document-service/status.md +++ b/docusaurus/docs/cms/api/document-service/status.md @@ -29,6 +29,8 @@ By default the [Document Service API](/cms/api/document-service) returns the dra Passing `{ status: 'draft' }` to a Document Service API query returns the same results as not passing any `status` parameter. ::: +For derived publication cohorts (never-published, modified, and others), see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). + ## Get the published version with `findOne()` {#find-one} `findOne()` queries return the draft version of a document by default. @@ -152,7 +154,7 @@ const publishedCount = await strapi.documents("api::restaurant.restaurant").coun :::note Since published documents necessarily also have a draft counterpart, a published document is still counted as having a draft version. -This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. There currently is no way to prevent already published documents from being counted. +This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. To count only never-published drafts, pass a [`publicationFilter`](/cms/api/document-service/publication-filter) value such as `'never-published'` or `'never-published-document'`. ::: ## Create a draft and publish it {#create} diff --git a/docusaurus/docs/cms/api/graphql.md b/docusaurus/docs/cms/api/graphql.md index fd49c12cca..1872075b24 100644 --- a/docusaurus/docs/cms/api/graphql.md +++ b/docusaurus/docs/cms/api/graphql.md @@ -425,6 +425,62 @@ query Query($status: PublicationStatus) { } ``` +### Filter by derived publication cohort {#publication-filter} + +If the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled, you can add a `publicationFilter` argument to built-in collection and single-type queries. The GraphQL plugin exposes the same cohorts as the REST API and Document Service API through the `PublicationFilter` enum. + +Combine `publicationFilter` with `status` the same way as for REST (see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter#status-combination)). + +When `status` is omitted, GraphQL defaults to `PUBLISHED` before applying `publicationFilter` (same as REST). Example: `restaurants(publicationFilter: MODIFIED)` returns published rows in the modified cohort; use `status: DRAFT` to return draft rows instead. + +Built-in root queries (for example `restaurants`, `restaurants_connection`) pass `publicationFilter` down to populated draft & publish relations on nested fields so relation results match the parent query cohort. + +```graphql title="Example: Fetch never-published draft documents" +query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { + restaurants(status: DRAFT, publicationFilter: NEVER_PUBLISHED) { + documentId + name + publishedAt + } +} +``` + +```graphql title="Example: Fetch published rows without a draft peer" +query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { + restaurants(status: PUBLISHED, publicationFilter: PUBLISHED_WITHOUT_DRAFT) { + documentId + name + publishedAt + } +} +``` + +```graphql title="Example: Modified cohort with default PUBLISHED status" +query Query { + restaurants(publicationFilter: MODIFIED) { + documentId + name + publishedAt + } +} +``` + +Available enum values: + +| GraphQL enum | Document Service / REST value | +| ------------ | ----------------------------- | +| `NEVER_PUBLISHED` | `never-published` | +| `HAS_PUBLISHED_VERSION` | `has-published-version` | +| `MODIFIED` | `modified` | +| `UNMODIFIED` | `unmodified` | +| `NEVER_PUBLISHED_DOCUMENT` | `never-published-document` | +| `HAS_PUBLISHED_VERSION_DOCUMENT` | `has-published-version-document` | +| `PUBLISHED_WITHOUT_DRAFT` | `published-without-draft` | +| `PUBLISHED_WITH_DRAFT` | `published-with-draft` | + +:::note +The deprecated `hasPublishedVersion` boolean argument is still accepted (`true` / `false`) and maps to `NEVER_PUBLISHED_DOCUMENT` / `HAS_PUBLISHED_VERSION_DOCUMENT`. If both `publicationFilter` and `hasPublishedVersion` are provided, `publicationFilter` takes precedence. Prefer `publicationFilter` for new queries. +::: ## Mutations diff --git a/docusaurus/docs/cms/api/rest/parameters.md b/docusaurus/docs/cms/api/rest/parameters.md index d683c6e6a5..175c577228 100644 --- a/docusaurus/docs/cms/api/rest/parameters.md +++ b/docusaurus/docs/cms/api/rest/parameters.md @@ -2,7 +2,7 @@ title: Parameters description: Use API parameters to refine your Strapi REST API queries. sidebar_label: Parameters -next: ./filtering-locale-publication.md +next: ./publication-filter.md tags: - API - Content API @@ -25,6 +25,7 @@ The following API parameters are available: | `filters` | Object | [Filter the response](/cms/api/rest/filters) | | `locale` | String | [Select a locale](/cms/api/rest/locale) | | `status` | String | [Select the Draft & Publish status](/cms/api/rest/status) | +| `publicationFilter` | String | [Select a derived Draft & Publish cohort](/cms/api/rest/publication-filter) | | `populate` | String or Object | [Populate relations, components, or dynamic zones](/cms/api/rest/populate-select#population) | | `fields` | Array | [Select only specific fields to display](/cms/api/rest/populate-select#field-selection) | | `sort` | String or Array | [Sort the response](/cms/api/rest/sort-pagination.md#sorting) | diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md new file mode 100644 index 0000000000..9fcc45a9be --- /dev/null +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -0,0 +1,205 @@ +--- +title: Publication filter +description: Use the publicationFilter parameter with Strapi's REST API to query derived Draft & Publish cohorts such as never-published or modified documents. +sidebarDepth: 3 +sidebar_label: Publication filter +displayed_sidebar: cmsSidebar +tags: +- API +- Content API +- find +- interactive query builder +- publicationFilter +- qs library +- REST API +- status +--- + +import QsForQueryBody from '/docs/snippets/qs-for-query-body.md' +import QsForQueryTitle from '/docs/snippets/qs-for-query-title.md' + +# REST API: `publicationFilter` + +The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. It selects derived publication cohorts while [`status`](/cms/api/rest/status) selects draft or published rows. + +:::prerequisites +The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. +::: + +## Default `status` {#default-status} + +When `status` is omitted, the REST API defaults to `status=published` **before** applying `publicationFilter`. + +| Query | Effective behavior | +| ----- | ------------------ | +| `?publicationFilter=never-published` | Empty (cohort is draft-only; default status is `published`) | +| `?status=draft&publicationFilter=never-published` | Never-published draft rows | +| `?publicationFilter=modified` | Published rows in the modified cohort | +| `?status=draft&publicationFilter=modified` | Draft rows in the modified cohort | +| `?publicationFilter=published-without-draft` | Orphan published rows (default `status=published` is correct) | + +The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status). + +Cohort definitions, the full `status` × `publicationFilter` matrix, Content Manager mapping, and validation rules are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). + +The REST API accepts the same kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. + +Invalid values return HTTP `400`. + +## Get never-published draft documents {#never-published} + +`GET /api/restaurants?status=draft&publicationFilter=never-published` + + + + + +`GET /api/restaurants?status=draft&publicationFilter=never-published` + + + +
+JavaScript query (built with the qs library): + + + +```js +const qs = require('qs'); +const query = qs.stringify( + { + status: 'draft', + publicationFilter: 'never-published', + }, + { + encodeValuesOnly: true, + } +); + +await request(`/api/restaurants?${query}`); +``` + +
+ + + +```json {6} +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "New Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + + + +
+ +## Get modified documents (default published slice) {#modified} + +With no `status` parameter, REST returns **published** rows in the modified cohort: + +`GET /api/restaurants?publicationFilter=modified` + +To get **draft** rows instead, add `status=draft`: + +`GET /api/restaurants?status=draft&publicationFilter=modified` + + + + + +`GET /api/restaurants?publicationFilter=modified` + + + +
+JavaScript query (built with the qs library): + + + +```js +const qs = require('qs'); +const query = qs.stringify( + { + publicationFilter: 'modified', + }, + { + encodeValuesOnly: true, + } +); + +await request(`/api/restaurants?${query}`); +``` + +
+ +
+ +## Get published rows without a draft peer {#published-without-draft} + +`GET /api/restaurants?status=published&publicationFilter=published-without-draft` + +Because REST defaults to `status=published`, `?publicationFilter=published-without-draft` alone is equivalent. + + + + + +`GET /api/restaurants?publicationFilter=published-without-draft` + + + +
+JavaScript query (built with the qs library): + + + +```js +const qs = require('qs'); +const query = qs.stringify( + { + publicationFilter: 'published-without-draft', + }, + { + encodeValuesOnly: true, + } +); + +await request(`/api/restaurants?${query}`); +``` + +
+ +
+ +## Combine with other parameters {#combine} + +`publicationFilter` can be combined with [`filters`](/cms/api/rest/filters), [`locale`](/cms/api/rest/locale), [`populate`](/cms/api/rest/populate-select), and other [REST parameters](/cms/api/rest/parameters). All conditions are applied together. + +## Deprecated `hasPublishedVersion` parameter {#has-published-version-deprecated} + +The boolean `hasPublishedVersion` query parameter is deprecated. Accepted values: `true`, `false`, `'true'`, or `'false'`. Strapi maps it to document-scoped `publicationFilter` values: + +| `hasPublishedVersion` | Maps to | +| --------------------- | ------- | +| `false` / `'false'` | `never-published-document` | +| `true` / `'true'` | `has-published-version-document` | + +Example: `GET /api/restaurants?status=draft&hasPublishedVersion=false` + +If both `publicationFilter` and `hasPublishedVersion` are sent, `publicationFilter` wins. + +Prefer `publicationFilter` for new integrations. diff --git a/docusaurus/docs/cms/api/rest/status.md b/docusaurus/docs/cms/api/rest/status.md index 2d38ff9a0f..c40ec49572 100644 --- a/docusaurus/docs/cms/api/rest/status.md +++ b/docusaurus/docs/cms/api/rest/status.md @@ -39,6 +39,8 @@ In the response data, the `publishedAt` field is `null` for drafts. Since published versions are returned by default, passing no status parameter is equivalent to passing `status=published`. ::: +For derived publication cohorts (never-published, modified, and others), see [REST API: `publicationFilter`](/cms/api/rest/publication-filter). +

diff --git a/docusaurus/docs/cms/features/draft-and-publish.md b/docusaurus/docs/cms/features/draft-and-publish.md index 9f72034c4e..2a52b18031 100644 --- a/docusaurus/docs/cms/features/draft-and-publish.md +++ b/docusaurus/docs/cms/features/draft-and-publish.md @@ -185,16 +185,19 @@ To unpublish several entries at the same time: ### Usage with APIs -Draft or published content can be requested, created, updated, and deleted using the `status` parameter through the various front-end APIs accessible from [Strapi's Content API](/cms/api/content-api): +Draft or published content can be requested, created, updated, and deleted using the `status` parameter through the various front-end APIs accessible from [Strapi's Content API](/cms/api/content-api). To query derived cohorts such as never-published or modified documents, use the `publicationFilter` parameter (REST and GraphQL) or the equivalent Document Service API option. + + On the back-end server of Strapi, the Document Service API can also be used to interact with localized content: + diff --git a/docusaurus/sidebars.js b/docusaurus/sidebars.js index bc344ead8d..f038c428b3 100644 --- a/docusaurus/sidebars.js +++ b/docusaurus/sidebars.js @@ -214,6 +214,7 @@ const sidebars = { 'cms/api/rest/filters', 'cms/api/rest/locale', 'cms/api/rest/status', + 'cms/api/rest/publication-filter', 'cms/api/rest/populate-select', 'cms/api/rest/relations', 'cms/api/rest/sort-pagination', @@ -249,6 +250,7 @@ const sidebars = { 'cms/api/document-service/populate', 'cms/api/document-service/sort-pagination', 'cms/api/document-service/status', + 'cms/api/document-service/publication-filter', ], }, ], diff --git a/docusaurus/static/llms-code.txt b/docusaurus/static/llms-code.txt index 6575ecdccc..d4e776e485 100644 --- a/docusaurus/static/llms-code.txt +++ b/docusaurus/static/llms-code.txt @@ -4911,6 +4911,159 @@ File path: N/A +# Using publicationFilter with the Document Service API +Source: https://docs.strapi.io/cms/api/document-service/publication-filter + +## Default status when publicationFilter is used +Description: Example with publicationFilter: 'modified' and no status: +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#default-status) + +Language: JavaScript +File path: N/A + +```js +// Document Service API → draft rows in the modified cohort +await strapi.documents('api::restaurant.restaurant').findMany({ + publicationFilter: 'modified', +}); + +// REST: GET /api/restaurants?publicationFilter=modified → published rows in the modified cohort +``` + + +## Query never-published drafts +Description: Valid but empty combinations do not return validation errors. +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#never-published) + +Language: JavaScript +File path: N/A + +```js +const documents = await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'never-published', +}); +``` + + +## Query has-published-version drafts +Description: Returns draft rows for (documentId, locale) pairs with no published version for that locale. +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#has-published-version) + +Language: JavaScript +File path: N/A + +```js +const documents = await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'has-published-version', +}); +``` + + +## Query modified or unmodified documents +Description: Returns draft rows where a published row also exists for the same (documentId, locale). +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#modified-unmodified) + +Language: JavaScript +File path: N/A + +```js +// Draft side of modified pairs +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'modified', +}); + +// Published side of unmodified pairs +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'published', + publicationFilter: 'unmodified', +}); +``` + + +## Query document-scoped cohorts +Description: Comparison uses updatedAt on the draft and published rows for the same pair. +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#document-scoped) + +Language: JavaScript +File path: N/A + +```js +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'never-published-document', +}); +``` + +Language: JavaScript +File path: N/A + +```js +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'has-published-version-document', +}); +``` + + +## Query published rows without or with a draft peer +Description: Returns draft rows for documents that have at least one published row in any locale (broader than pair-scoped has-published-version). +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#published-slice) + +Language: JavaScript +File path: N/A + +```js +// Orphan published rows (published row, no draft sibling for the same pair) +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'published', + publicationFilter: 'published-without-draft', +}); + +// Published rows that still have a draft sibling +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'published', + publicationFilter: 'published-with-draft', +}); +``` + + +## Use with findOne() and findFirst() +Description: publicationFilter applies the same cohort rules. +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#find-one-find-first) + +Language: JavaScript +File path: N/A + +```js +await strapi.documents('api::restaurant.restaurant').findOne({ + documentId: 'a1b2c3d4e5f6g7h8i9j0klm', + status: 'draft', + publicationFilter: 'never-published', +}); +``` + + +## Count documents in a cohort +Description: publicationFilter is merged with other query filters (logical AND). +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#count) + +Language: JavaScript +File path: N/A + +```js +const neverPublishedCount = await strapi + .documents('api::restaurant.restaurant') + .count({ + status: 'draft', + publicationFilter: 'never-published', + }); +``` + + + # Using Sort & Pagination with the Document Service API Source: https://docs.strapi.io/cms/api/document-service/sort-pagination @@ -6421,6 +6574,52 @@ query Query($status: PublicationStatus) { ``` +## Filter by derived publication cohort +Description: Built-in root queries (for example restaurants, restaurants_connection) pass publicationFilter down to populated draft & publish relations on nested fields so relation results match the parent query cohort. +(Source: https://docs.strapi.io/cms/api/graphql#publication-filter) + +Language: GRAPHQL +File path: Example: + +```graphql +query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { + restaurants(status: DRAFT, publicationFilter: NEVER_PUBLISHED) { + documentId + name + publishedAt + } +} +``` + +--- +Language: GRAPHQL +File path: Example: + +```graphql +query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { + restaurants(status: PUBLISHED, publicationFilter: PUBLISHED_WITHOUT_DRAFT) { + documentId + name + publishedAt + } +} +``` + +--- +Language: GRAPHQL +File path: Example: + +```graphql +query Query { + restaurants(publicationFilter: MODIFIED) { + documentId + name + publishedAt + } +} +``` + + ## Create a new document Description: The following example creates a new document for the "Restaurant" content-type and returns its name and documentId: (Source: https://docs.strapi.io/cms/api/graphql#create-a-new-document) @@ -10352,6 +10551,102 @@ File path: N/A +# Publication filter +Source: https://docs.strapi.io/cms/api/rest/publication-filter + +## Get never-published draft documents +Description: Code example from "Get never-published draft documents" +(Source: https://docs.strapi.io/cms/api/rest/publication-filter#never-published) + +Language: JavaScript +File path: N/A + +```js +const qs = require('qs'); +const query = qs.stringify( + { + status: 'draft', + publicationFilter: 'never-published', + }, + { + encodeValuesOnly: true, + } +); + +await request(`/api/restaurants?${query}`); +``` + +--- +Language: JSON +File path: N/A + +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "New Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + + +## Get modified documents (default published slice) +Description: Code example from "Get modified documents (default published slice)" +(Source: https://docs.strapi.io/cms/api/rest/publication-filter#modified) + +Language: JavaScript +File path: N/A + +```js +const qs = require('qs'); +const query = qs.stringify( + { + publicationFilter: 'modified', + }, + { + encodeValuesOnly: true, + } +); + +await request(`/api/restaurants?${query}`); +``` + + +## Get published rows without a draft peer +Description: Code example from "Get published rows without a draft peer" +(Source: https://docs.strapi.io/cms/api/rest/publication-filter#published-without-draft) + +Language: JavaScript +File path: N/A + +```js +const qs = require('qs'); +const query = qs.stringify( + { + publicationFilter: 'published-without-draft', + }, + { + encodeValuesOnly: true, + } +); + +await request(`/api/restaurants?${query}`); +``` + + + # Relations Source: https://docs.strapi.io/cms/api/rest/relations diff --git a/docusaurus/static/llms-full.txt b/docusaurus/static/llms-full.txt index ba548c7228..dba1b317b2 100644 --- a/docusaurus/static/llms-full.txt +++ b/docusaurus/static/llms-full.txt @@ -2528,6 +2528,7 @@ Syntax: `findFirst(parameters: Params) => Document` |-----------|-------------|---------|------| | [`locale`](/cms/api/document-service/locale#find-first) | Locale of the documents to find. | Default locale | String or `undefined` | | [`status`](/cms/api/document-service/status#find-first) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Publication status, can be: | `'draft'` | `'published'` or `'draft'` | +| [`publicationFilter`](/cms/api/document-service/publication-filter) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Derived publication cohort to match before applying `status` | - | String | | [`filters`](/cms/api/document-service/filters) | [Filters](/cms/api/document-service/filters) to use | `null` | Object | | [`fields`](/cms/api/document-service/fields#findfirst) | [Select fields](/cms/api/document-service/fields#findfirst) to return | All fields
(except those not populate by default) | Object | | [`populate`](/cms/api/document-service/populate) | [Populate](/cms/api/document-service/populate) results with additional fields. | `null` | Object | @@ -2562,6 +2563,7 @@ Syntax: `findMany(parameters: Params) => Document[]` |-----------|-------------|---------|------| | [`locale`](/cms/api/document-service/locale#find-many) | Locale of the documents to find. | Default locale | String or `undefined` | | [`status`](/cms/api/document-service/status#find-many) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Publication status, can be: | `'draft'` | `'published'` or `'draft'` | +| [`publicationFilter`](/cms/api/document-service/publication-filter) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Derived publication cohort to match before applying `status` | - | String | | [`filters`](/cms/api/document-service/filters) | [Filters](/cms/api/document-service/filters) to use | `null` | Object | | [`fields`](/cms/api/document-service/fields#findmany) | [Select fields](/cms/api/document-service/fields#findmany) to return | All fields
(except those not populate by default) | Object | | [`populate`](/cms/api/document-service/populate) | [Populate](/cms/api/document-service/populate) results with additional fields. | `null` | Object | @@ -2808,12 +2810,13 @@ Syntax: `count(parameters: Params) => number` |-----------|-------------|---------|------| | [`locale`](/cms/api/document-service/locale#count) | Locale of the documents to count | Default locale | String or `null` | | [`status`](/cms/api/document-service/status#count) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Publication status, can be: | `'draft'` | `'published'` or `'draft'` | +| [`publicationFilter`](/cms/api/document-service/publication-filter) | _If [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type_:
Derived publication cohort to match before applying `status` | - | String | | [`filters`](/cms/api/document-service/filters) | [Filters](/cms/api/document-service/filters) to use | `null` | Object | :::note Since published documents necessarily also have a draft counterpart, a published document is still counted as having a draft version. -This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. There currently is no way to prevent already published documents from being counted. +This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. To count only never-published drafts, pass a [`publicationFilter`](/cms/api/document-service/publication-filter) value such as `'never-published'` or `'never-published-document'`. ::: #### Examples @@ -3776,6 +3779,239 @@ To populate while deleting documents: +# Using publicationFilter with the Document Service API +Source: https://docs.strapi.io/cms/api/document-service/publication-filter + +# Document Service API: `publicationFilter` + +The [`status`](/cms/api/document-service/status) parameter selects which **row slice** to read for each document: `draft` rows have `publishedAt: null`, and `published` rows have a non-null `publishedAt`. + +The optional `publicationFilter` parameter selects a **derived publication cohort** first: a set of `(documentId, locale)` pairs (or `documentId` only when [Internationalization (i18n)](/cms/features/internationalization) is disabled) defined by how draft and published rows relate. Strapi then returns the row that matches both the cohort and the resolved `status`. + +:::prerequisites +The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. +::: + +`publicationFilter` is supported on `findOne()`, `findFirst()`, `findMany()`, and `count()`. It can be combined with [`filters`](/cms/api/document-service/filters), [`populate`](/cms/api/document-service/populate), and other query parameters. Invalid values raise a validation error. + +## Default `status` when `publicationFilter` is used {#default-status} + +`publicationFilter` is applied **after** `status` is resolved (explicitly or by default). Defaults differ by API surface: + +| API surface | Default `status` when omitted | +| ----------- | ----------------------------- | +| Document Service API (direct) | `'draft'` | +| [REST API](/cms/api/rest/publication-filter) | `'published'` | +| [GraphQL API](/cms/api/graphql#publication-filter) | `PUBLISHED` | + +Example with `publicationFilter: 'modified'` and no `status`: + +```js +// Document Service API → draft rows in the modified cohort +await strapi.documents('api::restaurant.restaurant').findMany({ + publicationFilter: 'modified', +}); + +// REST: GET /api/restaurants?publicationFilter=modified → published rows in the modified cohort +``` + +Pair-scoped modes such as `never-published` only include draft rows in the cohort. With REST or GraphQL defaults (`status=published`), those queries return an empty result set unless you pass `status=draft` / `status: DRAFT`. + +## Available values {#values} + +REST and the Document Service API use kebab-case strings. GraphQL exposes the same cohorts through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). + +| Value | Scope | Cohort definition (which `(documentId, locale)` pairs match) | +| ----- | ----- | -------------------------------------------------------------- | +| `never-published` | Pair | No row with non-null `publishedAt` exists for the same `(documentId, locale)` | +| `has-published-version` | Pair | **Both** a draft row and a published row exist for the same `(documentId, locale)` | +| `modified` | Pair | Both slices exist and `draft.updatedAt > published.updatedAt` | +| `unmodified` | Pair | Both slices exist and `draft.updatedAt <= published.updatedAt` | +| `never-published-document` | Document | No row with non-null `publishedAt` exists for the same `documentId` in **any** locale | +| `has-published-version-document` | Document | At least one published row exists for the same `documentId` in **any** locale | +| `published-without-draft` | Pair | A published row exists for the pair and **no** draft row exists for the same `(documentId, locale)` | +| `published-with-draft` | Pair | A published row exists for the pair and a draft row **also** exists for the same `(documentId, locale)` | + +For content-types without i18n, read `(documentId, locale)` as `documentId` only. + +### Semantics notes {#semantics} + +- **`has-published-version` excludes orphan published rows**: If only a published row exists for a pair (no draft sibling), that pair is **not** in the `has-published-version` cohort. Orphan published rows can appear under `published-without-draft` when querying with `status: 'published'`. +- **`modified` / `unmodified` require both slices**: Pairs with only a draft or only a published row are not included. +- **`modified` ∪ `unmodified` = `has-published-version`** (for the same `status`): The two modes partition pairs that have both slices. +- **Document-scoped modes**: Existence checks use `documentId` only. A document with draft EN + published NL qualifies for `has-published-version-document` even though EN is never published at the pair level. +- **Published-slice diagnostics** (`published-without-draft`, `published-with-draft`): Only select published rows. They are degenerate (empty) with `status: 'draft'`. + +### Content Manager list filters {#content-manager} + +The Content Manager **Status** filter (`__status`) is translated server-side. Only the **Draft (never published)** option uses `publicationFilter`: + +| Content Manager filter | Document Service query equivalent | +| ---------------------- | --------------------------------- | +| Draft (never published) | `status: 'draft'`, `publicationFilter: 'never-published-document'` | +| Published (all) | `status: 'published'` (no `publicationFilter`) | +| Published (modified) | Internal `publicationStatusFilter` (not a public REST/GraphQL parameter); similar intent to `status: 'published'` + `publicationFilter: 'modified'` but implemented separately in the Content Manager API | +| Published (unmodified) | Internal `publicationStatusFilter` (not a public REST/GraphQL parameter) | + +The **Draft (never published)** filter is document-scoped (`never-published-document`), not pair-scoped `never-published`. + +## Combine `status` and `publicationFilter` {#status-combination} + +| `status` | `publicationFilter` | Rows returned | +| -------- | ------------------- | ------------- | +| `draft` | `never-published` | Draft rows for pairs never published in that locale | +| `published` | `never-published` | Empty (degenerate) | +| `draft` | `has-published-version` | Draft rows for pairs that also have a published version | +| `published` | `has-published-version` | Published rows for pairs that also have a draft version (excludes orphan published-only pairs) | +| `draft` | `modified` | Draft rows newer than their published peer | +| `published` | `modified` | Published rows whose draft peer is newer | +| `draft` | `unmodified` | Draft rows not newer than their published peer | +| `published` | `unmodified` | Published rows whose draft peer is not newer | +| `draft` | `never-published-document` | Draft rows whose document has no published row in any locale | +| `published` | `never-published-document` | Empty (degenerate) | +| `draft` | `has-published-version-document` | Draft rows whose document has at least one published row (any locale) | +| `published` | `has-published-version-document` | Published rows whose document has at least one draft row (any locale) | +| `published` | `published-without-draft` | Published rows with no draft sibling for the same pair | +| `draft` | `published-without-draft` | Empty (degenerate) | +| `published` | `published-with-draft` | Published rows that have a draft sibling for the same pair | +| `draft` | `published-with-draft` | Empty (degenerate) | + +Valid but empty combinations do not return validation errors. + +## Query never-published drafts {#never-published} + +```js +const documents = await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'never-published', +}); +``` + +Returns draft rows for `(documentId, locale)` pairs with no published version for that locale. + +## Query has-published-version drafts {#has-published-version} + +```js +const documents = await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'has-published-version', +}); +``` + +Returns draft rows where a published row also exists for the same `(documentId, locale)`. Does not return draft rows for pairs that only exist as orphan published rows. + +## Query modified or unmodified documents {#modified-unmodified} + +```js +// Draft side of modified pairs +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'modified', +}); + +// Published side of unmodified pairs +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'published', + publicationFilter: 'unmodified', +}); +``` + +Comparison uses `updatedAt` on the draft and published rows for the same pair. + +## Query document-scoped cohorts {#document-scoped} + +```js +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'never-published-document', +}); +``` + +Returns draft rows for documents that have **never** been published in any locale. A multi-locale document with one published locale is excluded entirely, including its draft-only locales. + +```js +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'has-published-version-document', +}); +``` + +Returns draft rows for documents that have at least one published row in any locale (broader than pair-scoped `has-published-version`). + +## Query published rows without or with a draft peer {#published-slice} + +```js +// Orphan published rows (published row, no draft sibling for the same pair) +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'published', + publicationFilter: 'published-without-draft', +}); + +// Published rows that still have a draft sibling +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'published', + publicationFilter: 'published-with-draft', +}); +``` + +`published-without-draft` and `published-with-draft` partition published rows per `(documentId, locale)` (excluding pairs with no published row). + +## Use with `findOne()` and `findFirst()` {#find-one-find-first} + +`publicationFilter` applies the same cohort rules. If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists. + +```js +await strapi.documents('api::restaurant.restaurant').findOne({ + documentId: 'a1b2c3d4e5f6g7h8i9j0klm', + status: 'draft', + publicationFilter: 'never-published', +}); +``` + +## Combine with `filters` and `populate` {#filters-populate} + +`publicationFilter` is merged with other query filters (logical AND). When [populating relations](/cms/api/document-service/populate), nested queries on draft & publish content-types inherit the same cohort logic so populated results stay consistent with the parent query. + +## Count documents in a cohort {#count} + +```js +const neverPublishedCount = await strapi + .documents('api::restaurant.restaurant') + .count({ + status: 'draft', + publicationFilter: 'never-published', + }); +``` + +Without `publicationFilter`, `count({ status: 'draft' })` still counts every draft row, including drafts whose document already has a published version. Use `publicationFilter: 'never-published'` or `'never-published-document'` to count only never-published cohorts (see [`status` documentation](/cms/api/document-service/status#count)). + +## Validation {#validation} + +Unknown `publicationFilter` values are rejected: + +- Document Service API: throws a validation error. +- REST API: returns HTTP `400`. +- GraphQL: invalid enum values fail at query validation. + +## Deprecated `hasPublishedVersion` parameter {#has-published-version-deprecated} + +The boolean `hasPublishedVersion` parameter is deprecated in favor of `publicationFilter`. Strapi still accepts it on the REST API, GraphQL, and Document Service API and maps it to **document-scoped** modes: + +| `hasPublishedVersion` | Maps to | +| --------------------- | ------- | +| `false` (or string `'false'`) | `never-published-document` | +| `true` (or string `'true'`) | `has-published-version-document` | + +If both `publicationFilter` and `hasPublishedVersion` are passed, `publicationFilter` takes precedence. + +REST and GraphQL examples: [REST API: `publicationFilter`](/cms/api/rest/publication-filter#has-published-version-deprecated), [GraphQL API: `publicationFilter`](/cms/api/graphql#publication-filter). + +## Why not filter on `publishedAt` alone? {#why-not-published-at} + +A single row's `publishedAt` only describes that row. Cohorts such as `never-published`, `has-published-version`, and `modified` require comparing or correlating **two rows** for the same `(documentId, locale)`. `publicationFilter` encodes those rules in one server-side query instead of multiple client round-trips. + + + # Using Sort & Pagination with the Document Service API Source: https://docs.strapi.io/cms/api/document-service/sort-pagination @@ -3822,6 +4058,8 @@ By default the [Document Service API](/cms/api/document-service) returns the dra Passing `{ status: 'draft' }` to a Document Service API query returns the same results as not passing any `status` parameter. ::: +For derived publication cohorts (never-published, modified, and others), see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). + ## Get the published version with `findOne()` {#find-one} `findOne()` queries return the draft version of a document by default. @@ -3867,7 +4105,7 @@ const publishedCount = await strapi.documents("api::restaurant.restaurant").coun :::note Since published documents necessarily also have a draft counterpart, a published document is still counted as having a draft version. -This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. There currently is no way to prevent already published documents from being counted. +This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. To count only never-published drafts, pass a [`publicationFilter`](/cms/api/document-service/publication-filter) value such as `'never-published'` or `'never-published-document'`. ::: ## Create a draft and publish it {#create} @@ -4515,6 +4753,7 @@ The following API parameters are available: | `filters` | Object | [Filter the response](/cms/api/rest/filters) | | `locale` | String | [Select a locale](/cms/api/rest/locale) | | `status` | String | [Select the Draft & Publish status](/cms/api/rest/status) | +| `publicationFilter` | String | [Select a derived Draft & Publish cohort](/cms/api/rest/publication-filter) | | `populate` | String or Object | [Populate relations, components, or dynamic zones](/cms/api/rest/populate-select#population) | | `fields` | Array | [Select only specific fields to display](/cms/api/rest/populate-select#field-selection) | | `sort` | String or Array | [Sort the response](/cms/api/rest/sort-pagination.md#sorting) | @@ -4608,6 +4847,89 @@ In production, always use explicit population instead of wildcards like `populat +# Publication filter +Source: https://docs.strapi.io/cms/api/rest/publication-filter + +# REST API: `publicationFilter` + +The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. It selects derived publication cohorts while [`status`](/cms/api/rest/status) selects draft or published rows. + +:::prerequisites +The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. +::: + +## Default `status` {#default-status} + +When `status` is omitted, the REST API defaults to `status=published` **before** applying `publicationFilter`. + +| Query | Effective behavior | +| ----- | ------------------ | +| `?publicationFilter=never-published` | Empty (cohort is draft-only; default status is `published`) | +| `?status=draft&publicationFilter=never-published` | Never-published draft rows | +| `?publicationFilter=modified` | Published rows in the modified cohort | +| `?status=draft&publicationFilter=modified` | Draft rows in the modified cohort | +| `?publicationFilter=published-without-draft` | Orphan published rows (default `status=published` is correct) | + +The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status). + +Cohort definitions, the full `status` × `publicationFilter` matrix, Content Manager mapping, and validation rules are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). + +The REST API accepts the same kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. + +Invalid values return HTTP `400`. + +## Get never-published draft documents {#never-published} + +`GET /api/restaurants?status=draft&publicationFilter=never-published` + +
+JavaScript query (built with the qs library): + + + +## Get modified documents (default published slice) {#modified} + +With no `status` parameter, REST returns **published** rows in the modified cohort: + +`GET /api/restaurants?publicationFilter=modified` + +To get **draft** rows instead, add `status=draft`: + +`GET /api/restaurants?status=draft&publicationFilter=modified` + +
+JavaScript query (built with the qs library): + +## Get published rows without a draft peer {#published-without-draft} + +`GET /api/restaurants?status=published&publicationFilter=published-without-draft` + +Because REST defaults to `status=published`, `?publicationFilter=published-without-draft` alone is equivalent. + +
+JavaScript query (built with the qs library): + +## Combine with other parameters {#combine} + +`publicationFilter` can be combined with [`filters`](/cms/api/rest/filters), [`locale`](/cms/api/rest/locale), [`populate`](/cms/api/rest/populate-select), and other [REST parameters](/cms/api/rest/parameters). All conditions are applied together. + +## Deprecated `hasPublishedVersion` parameter {#has-published-version-deprecated} + +The boolean `hasPublishedVersion` query parameter is deprecated. Accepted values: `true`, `false`, `'true'`, or `'false'`. Strapi maps it to document-scoped `publicationFilter` values: + +| `hasPublishedVersion` | Maps to | +| --------------------- | ------- | +| `false` / `'false'` | `never-published-document` | +| `true` / `'true'` | `has-published-version-document` | + +Example: `GET /api/restaurants?status=draft&hasPublishedVersion=false` + +If both `publicationFilter` and `hasPublishedVersion` are sent, `publicationFilter` wins. + +Prefer `publicationFilter` for new integrations. + + + # Relations Source: https://docs.strapi.io/cms/api/rest/relations @@ -4895,6 +5217,8 @@ In the response data, the `publishedAt` field is `null` for drafts. Since published versions are returned by default, passing no status parameter is equivalent to passing `status=published`. ::: +For derived publication cohorts (never-published, modified, and others), see [REST API: `publicationFilter`](/cms/api/rest/publication-filter). +

diff --git a/docusaurus/static/llms.txt b/docusaurus/static/llms.txt index 256af874dc..1871c20003 100644 --- a/docusaurus/static/llms.txt +++ b/docusaurus/static/llms.txt @@ -42,6 +42,7 @@ - [Using the locale parameter with the Document Service API](https://docs.strapi.io/cms/api/document-service/locale): Use Strapi's Document Service API to work with locale versions with your queries. - [Extending the Document Service behavior](https://docs.strapi.io/cms/api/document-service/middlewares): This document provides information about the middlewares in the Document Service API. - [Using Populate with the Document Service API](https://docs.strapi.io/cms/api/document-service/populate): Use Strapi's Document Service API to populate or select some fields. +- [Using publicationFilter with the Document Service API](https://docs.strapi.io/cms/api/document-service/publication-filter): Use the publicationFilter parameter with Strapi's Document Service API to query derived Draft & Publish cohorts such as never-published or modified documents. - [Using Sort & Pagination with the Document Service API](https://docs.strapi.io/cms/api/document-service/sort-pagination): Use Strapi's Document Service API to sort and paginate query results - [Using Draft & Publish with the Document Service API](https://docs.strapi.io/cms/api/document-service/status): Use Strapi's Document Service API to return either the draft or the published version of a document - [GraphQL API](https://docs.strapi.io/cms/api/graphql): import DeepFilteringBlogLink from '/docs/snippets/deep-filtering-blog.md' @@ -53,6 +54,7 @@ - [Locale](https://docs.strapi.io/cms/api/rest/locale): Browse the REST API reference for the locale parameter to take advantage of the Internationalization feature through REST. - [Parameters](https://docs.strapi.io/cms/api/rest/parameters): Use API parameters to refine your Strapi REST API queries. - [Populate and Select](https://docs.strapi.io/cms/api/rest/populate-select): Use the populate parameter to include relations, media fields, components, and dynamic zones in REST API responses. Use the fields parameter to return only specific fields. +- [Publication filter](https://docs.strapi.io/cms/api/rest/publication-filter): Use the publicationFilter parameter with Strapi's REST API to query derived Draft & Publish cohorts such as never-published or modified documents. - [Relations](https://docs.strapi.io/cms/api/rest/relations): Use the REST API to manage the order of relations - [Sort and Pagination](https://docs.strapi.io/cms/api/rest/sort-pagination): Use Strapi's REST API to sort or paginate your data. - [Status](https://docs.strapi.io/cms/api/rest/status): Use Strapi's REST API to work with draft or published versions of your documents. From 1c3b0825c5193713b71d7510dc6464f805e3595b Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Thu, 18 Jun 2026 15:04:59 +0200 Subject: [PATCH 02/75] Polish publicationFilter docs after review. Fix REST page navigation and example consistency, tighten Document Service prose for readers and llms-code extraction, and regenerate LLM artifacts. --- .../document-service/publication-filter.md | 42 ++++++---- docusaurus/docs/cms/api/rest/parameters.md | 2 +- .../docs/cms/api/rest/publication-filter.md | 78 ++++++++++++++----- docusaurus/docs/cms/api/rest/status.md | 1 + .../docs/cms/features/draft-and-publish.md | 2 +- docusaurus/static/llms-code.txt | 68 +++++++++++++--- docusaurus/static/llms-full.txt | 74 +++++++++--------- 7 files changed, 186 insertions(+), 81 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index ef11cb1ecd..69c7ac56c7 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -38,7 +38,7 @@ The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled o | [REST API](/cms/api/rest/publication-filter) | `'published'` | | [GraphQL API](/cms/api/graphql#publication-filter) | `PUBLISHED` | -Example with `publicationFilter: 'modified'` and no `status`: +The following example compares Document Service and REST behavior when only `publicationFilter: 'modified'` is passed: ```js // Document Service API → draft rows in the modified cohort @@ -74,7 +74,7 @@ For content-types without i18n, read `(documentId, locale)` as `documentId` only - **`modified` / `unmodified` require both slices**: Pairs with only a draft or only a published row are not included. - **`modified` ∪ `unmodified` = `has-published-version`** (for the same `status`): The two modes partition pairs that have both slices. - **Document-scoped modes**: Existence checks use `documentId` only. A document with draft EN + published NL qualifies for `has-published-version-document` even though EN is never published at the pair level. -- **Published-slice diagnostics** (`published-without-draft`, `published-with-draft`): Only select published rows. They are degenerate (empty) with `status: 'draft'`. +- **Published-slice diagnostics** (`published-without-draft`, `published-with-draft`): Only select published rows. They return no rows when `status` is `'draft'`. ### Content Manager list filters {#content-manager} @@ -94,7 +94,7 @@ The **Draft (never published)** filter is document-scoped (`never-published-docu | `status` | `publicationFilter` | Rows returned | | -------- | ------------------- | ------------- | | `draft` | `never-published` | Draft rows for pairs never published in that locale | -| `published` | `never-published` | Empty (degenerate) | +| `published` | `never-published` | Empty | | `draft` | `has-published-version` | Draft rows for pairs that also have a published version | | `published` | `has-published-version` | Published rows for pairs that also have a draft version (excludes orphan published-only pairs) | | `draft` | `modified` | Draft rows newer than their published peer | @@ -102,18 +102,22 @@ The **Draft (never published)** filter is document-scoped (`never-published-docu | `draft` | `unmodified` | Draft rows not newer than their published peer | | `published` | `unmodified` | Published rows whose draft peer is not newer | | `draft` | `never-published-document` | Draft rows whose document has no published row in any locale | -| `published` | `never-published-document` | Empty (degenerate) | +| `published` | `never-published-document` | Empty | | `draft` | `has-published-version-document` | Draft rows whose document has at least one published row (any locale) | | `published` | `has-published-version-document` | Published rows whose document has at least one draft row (any locale) | | `published` | `published-without-draft` | Published rows with no draft sibling for the same pair | -| `draft` | `published-without-draft` | Empty (degenerate) | +| `draft` | `published-without-draft` | Empty | | `published` | `published-with-draft` | Published rows that have a draft sibling for the same pair | -| `draft` | `published-with-draft` | Empty (degenerate) | +| `draft` | `published-with-draft` | Empty | +:::note Valid but empty combinations do not return validation errors. +::: ## Query never-published drafts {#never-published} +Return draft rows for `(documentId, locale)` pairs with no published version for that locale: + ```js const documents = await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', @@ -121,10 +125,10 @@ const documents = await strapi.documents('api::restaurant.restaurant').findMany( }); ``` -Returns draft rows for `(documentId, locale)` pairs with no published version for that locale. - ## Query has-published-version drafts {#has-published-version} +Return draft rows where a published row also exists for the same `(documentId, locale)`. Orphan published-only pairs are excluded: + ```js const documents = await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', @@ -132,10 +136,10 @@ const documents = await strapi.documents('api::restaurant.restaurant').findMany( }); ``` -Returns draft rows where a published row also exists for the same `(documentId, locale)`. Does not return draft rows for pairs that only exist as orphan published rows. - ## Query modified or unmodified documents {#modified-unmodified} +Compare `updatedAt` on the draft and published rows for the same pair: + ```js // Draft side of modified pairs await strapi.documents('api::restaurant.restaurant').findMany({ @@ -150,10 +154,10 @@ await strapi.documents('api::restaurant.restaurant').findMany({ }); ``` -Comparison uses `updatedAt` on the draft and published rows for the same pair. - ## Query document-scoped cohorts {#document-scoped} +Return draft rows for documents that have never been published in any locale: + ```js await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', @@ -161,7 +165,9 @@ await strapi.documents('api::restaurant.restaurant').findMany({ }); ``` -Returns draft rows for documents that have **never** been published in any locale. A multi-locale document with one published locale is excluded entirely, including its draft-only locales. +A multi-locale document with one published locale is excluded entirely, including its draft-only locales. + +Return draft rows for documents that have at least one published row in any locale: ```js await strapi.documents('api::restaurant.restaurant').findMany({ @@ -170,10 +176,12 @@ await strapi.documents('api::restaurant.restaurant').findMany({ }); ``` -Returns draft rows for documents that have at least one published row in any locale (broader than pair-scoped `has-published-version`). +This is broader than pair-scoped `has-published-version`. ## Query published rows without or with a draft peer {#published-slice} +`published-without-draft` and `published-with-draft` partition published rows per `(documentId, locale)` (excluding pairs with no published row): + ```js // Orphan published rows (published row, no draft sibling for the same pair) await strapi.documents('api::restaurant.restaurant').findMany({ @@ -188,11 +196,9 @@ await strapi.documents('api::restaurant.restaurant').findMany({ }); ``` -`published-without-draft` and `published-with-draft` partition published rows per `(documentId, locale)` (excluding pairs with no published row). - ## Use with `findOne()` and `findFirst()` {#find-one-find-first} -`publicationFilter` applies the same cohort rules. If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists. +`publicationFilter` applies the same cohort rules. If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: ```js await strapi.documents('api::restaurant.restaurant').findOne({ @@ -208,6 +214,8 @@ await strapi.documents('api::restaurant.restaurant').findOne({ ## Count documents in a cohort {#count} +Count draft rows in the never-published cohort: + ```js const neverPublishedCount = await strapi .documents('api::restaurant.restaurant') diff --git a/docusaurus/docs/cms/api/rest/parameters.md b/docusaurus/docs/cms/api/rest/parameters.md index 175c577228..6e9a7997a2 100644 --- a/docusaurus/docs/cms/api/rest/parameters.md +++ b/docusaurus/docs/cms/api/rest/parameters.md @@ -2,7 +2,7 @@ title: Parameters description: Use API parameters to refine your Strapi REST API queries. sidebar_label: Parameters -next: ./publication-filter.md +next: ./filters.md tags: - API - Content API diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 9fcc45a9be..0007449f2c 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -3,6 +3,7 @@ title: Publication filter description: Use the publicationFilter parameter with Strapi's REST API to query derived Draft & Publish cohorts such as never-published or modified documents. sidebarDepth: 3 sidebar_label: Publication filter +next: ./populate-select.md displayed_sidebar: cmsSidebar tags: - API @@ -16,11 +17,10 @@ tags: --- import QsForQueryBody from '/docs/snippets/qs-for-query-body.md' -import QsForQueryTitle from '/docs/snippets/qs-for-query-title.md' # REST API: `publicationFilter` -The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. It selects derived publication cohorts while [`status`](/cms/api/rest/status) selects draft or published rows. +The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. Use it to query derived publication cohorts such as never-published or modified documents. The [`status`](/cms/api/rest/status) parameter still selects whether each matching document returns its draft or published row. :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. @@ -40,15 +40,15 @@ When `status` is omitted, the REST API defaults to `status=published` **before** The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status). +:::note Cohort definitions, the full `status` × `publicationFilter` matrix, Content Manager mapping, and validation rules are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). +::: -The REST API accepts the same kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. - -Invalid values return HTTP `400`. +Accepted kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. Invalid values return HTTP `400`. ## Get never-published draft documents {#never-published} -`GET /api/restaurants?status=draft&publicationFilter=never-published` +Pair-scoped `never-published` only matches draft rows. Pass `status=draft` because REST defaults to `status=published`. @@ -107,15 +107,9 @@ await request(`/api/restaurants?${query}`); -## Get modified documents (default published slice) {#modified} - -With no `status` parameter, REST returns **published** rows in the modified cohort: - -`GET /api/restaurants?publicationFilter=modified` - -To get **draft** rows instead, add `status=draft`: +## Get modified documents {#modified} -`GET /api/restaurants?status=draft&publicationFilter=modified` +The `modified` cohort includes pairs where the draft row is newer than its published peer. With no `status` parameter, REST returns **published** rows from that cohort. Pass `status=draft` to return the draft rows instead. @@ -146,13 +140,36 @@ await request(`/api/restaurants?${query}`);
+ + +```json {6} +{ + "data": [ + { + "documentId": "znrlzntu9ei5onjvwfaalu2v", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + + + ## Get published rows without a draft peer {#published-without-draft} -`GET /api/restaurants?status=published&publicationFilter=published-without-draft` - -Because REST defaults to `status=published`, `?publicationFilter=published-without-draft` alone is equivalent. +The `published-without-draft` cohort matches published rows that have no draft sibling for the same `(documentId, locale)`. Because REST defaults to `status=published`, you can omit `status` in the query URL. @@ -183,6 +200,31 @@ await request(`/api/restaurants?${query}`);
+ + +```json {6} +{ + "data": [ + { + "documentId": "abcdefghijklmno456", + "name": "Legacy Restaurant", + "publishedAt": "2024-01-10T09:15:00.000Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + + + ## Combine with other parameters {#combine} @@ -200,6 +242,6 @@ The boolean `hasPublishedVersion` query parameter is deprecated. Accepted values Example: `GET /api/restaurants?status=draft&hasPublishedVersion=false` -If both `publicationFilter` and `hasPublishedVersion` are sent, `publicationFilter` wins. +If both `publicationFilter` and `hasPublishedVersion` are sent, `publicationFilter` takes precedence. Prefer `publicationFilter` for new integrations. diff --git a/docusaurus/docs/cms/api/rest/status.md b/docusaurus/docs/cms/api/rest/status.md index c40ec49572..da148fe0a0 100644 --- a/docusaurus/docs/cms/api/rest/status.md +++ b/docusaurus/docs/cms/api/rest/status.md @@ -3,6 +3,7 @@ title: Status description: Use Strapi's REST API to work with draft or published versions of your documents. sidebarDepth: 3 sidebar_label: Status +next: ./publication-filter.md displayed_sidebar: cmsSidebar tags: - API diff --git a/docusaurus/docs/cms/features/draft-and-publish.md b/docusaurus/docs/cms/features/draft-and-publish.md index 2a52b18031..4e570e5d23 100644 --- a/docusaurus/docs/cms/features/draft-and-publish.md +++ b/docusaurus/docs/cms/features/draft-and-publish.md @@ -194,7 +194,7 @@ Draft or published content can be requested, created, updated, and deleted using -On the back-end server of Strapi, the Document Service API can also be used to interact with localized content: +On the back-end server of Strapi, the Document Service API can also query and manage draft and published content: diff --git a/docusaurus/static/llms-code.txt b/docusaurus/static/llms-code.txt index d4e776e485..16b41558bc 100644 --- a/docusaurus/static/llms-code.txt +++ b/docusaurus/static/llms-code.txt @@ -4915,7 +4915,7 @@ File path: N/A Source: https://docs.strapi.io/cms/api/document-service/publication-filter ## Default status when publicationFilter is used -Description: Example with publicationFilter: 'modified' and no status: +Description: The following example compares Document Service and REST behavior when only publicationFilter: 'modified' is passed: (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#default-status) Language: JavaScript @@ -4932,7 +4932,7 @@ await strapi.documents('api::restaurant.restaurant').findMany({ ## Query never-published drafts -Description: Valid but empty combinations do not return validation errors. +Description: Return draft rows for (documentId, locale) pairs with no published version for that locale: (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#never-published) Language: JavaScript @@ -4947,7 +4947,7 @@ const documents = await strapi.documents('api::restaurant.restaurant').findMany( ## Query has-published-version drafts -Description: Returns draft rows for (documentId, locale) pairs with no published version for that locale. +Description: Return draft rows where a published row also exists for the same (documentId, locale). (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#has-published-version) Language: JavaScript @@ -4962,7 +4962,7 @@ const documents = await strapi.documents('api::restaurant.restaurant').findMany( ## Query modified or unmodified documents -Description: Returns draft rows where a published row also exists for the same (documentId, locale). +Description: Compare updatedAt on the draft and published rows for the same pair: (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#modified-unmodified) Language: JavaScript @@ -4984,7 +4984,7 @@ await strapi.documents('api::restaurant.restaurant').findMany({ ## Query document-scoped cohorts -Description: Comparison uses updatedAt on the draft and published rows for the same pair. +Description: Return draft rows for documents that have never been published in any locale: (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#document-scoped) Language: JavaScript @@ -5009,7 +5009,7 @@ await strapi.documents('api::restaurant.restaurant').findMany({ ## Query published rows without or with a draft peer -Description: Returns draft rows for documents that have at least one published row in any locale (broader than pair-scoped has-published-version). +Description: published-without-draft and published-with-draft partition published rows per (documentId, locale) (excluding pairs with no published row): (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#published-slice) Language: JavaScript @@ -5047,7 +5047,7 @@ await strapi.documents('api::restaurant.restaurant').findOne({ ## Count documents in a cohort -Description: publicationFilter is merged with other query filters (logical AND). +Description: Count draft rows in the never-published cohort: (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#count) Language: JavaScript @@ -10602,8 +10602,8 @@ File path: N/A ``` -## Get modified documents (default published slice) -Description: Code example from "Get modified documents (default published slice)" +## Get modified documents +Description: Code example from "Get modified documents" (Source: https://docs.strapi.io/cms/api/rest/publication-filter#modified) Language: JavaScript @@ -10623,6 +10623,31 @@ const query = qs.stringify( await request(`/api/restaurants?${query}`); ``` +--- +Language: JSON +File path: N/A + +```json +{ + "data": [ + { + "documentId": "znrlzntu9ei5onjvwfaalu2v", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + ## Get published rows without a draft peer Description: Code example from "Get published rows without a draft peer" @@ -10645,6 +10670,31 @@ const query = qs.stringify( await request(`/api/restaurants?${query}`); ``` +--- +Language: JSON +File path: N/A + +```json +{ + "data": [ + { + "documentId": "abcdefghijklmno456", + "name": "Legacy Restaurant", + "publishedAt": "2024-01-10T09:15:00.000Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + # Relations diff --git a/docusaurus/static/llms-full.txt b/docusaurus/static/llms-full.txt index dba1b317b2..8f5c006263 100644 --- a/docusaurus/static/llms-full.txt +++ b/docusaurus/static/llms-full.txt @@ -3804,7 +3804,7 @@ The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled o | [REST API](/cms/api/rest/publication-filter) | `'published'` | | [GraphQL API](/cms/api/graphql#publication-filter) | `PUBLISHED` | -Example with `publicationFilter: 'modified'` and no `status`: +The following example compares Document Service and REST behavior when only `publicationFilter: 'modified'` is passed: ```js // Document Service API → draft rows in the modified cohort @@ -3840,7 +3840,7 @@ For content-types without i18n, read `(documentId, locale)` as `documentId` only - **`modified` / `unmodified` require both slices**: Pairs with only a draft or only a published row are not included. - **`modified` ∪ `unmodified` = `has-published-version`** (for the same `status`): The two modes partition pairs that have both slices. - **Document-scoped modes**: Existence checks use `documentId` only. A document with draft EN + published NL qualifies for `has-published-version-document` even though EN is never published at the pair level. -- **Published-slice diagnostics** (`published-without-draft`, `published-with-draft`): Only select published rows. They are degenerate (empty) with `status: 'draft'`. +- **Published-slice diagnostics** (`published-without-draft`, `published-with-draft`): Only select published rows. They return no rows when `status` is `'draft'`. ### Content Manager list filters {#content-manager} @@ -3860,7 +3860,7 @@ The **Draft (never published)** filter is document-scoped (`never-published-docu | `status` | `publicationFilter` | Rows returned | | -------- | ------------------- | ------------- | | `draft` | `never-published` | Draft rows for pairs never published in that locale | -| `published` | `never-published` | Empty (degenerate) | +| `published` | `never-published` | Empty | | `draft` | `has-published-version` | Draft rows for pairs that also have a published version | | `published` | `has-published-version` | Published rows for pairs that also have a draft version (excludes orphan published-only pairs) | | `draft` | `modified` | Draft rows newer than their published peer | @@ -3868,18 +3868,22 @@ The **Draft (never published)** filter is document-scoped (`never-published-docu | `draft` | `unmodified` | Draft rows not newer than their published peer | | `published` | `unmodified` | Published rows whose draft peer is not newer | | `draft` | `never-published-document` | Draft rows whose document has no published row in any locale | -| `published` | `never-published-document` | Empty (degenerate) | +| `published` | `never-published-document` | Empty | | `draft` | `has-published-version-document` | Draft rows whose document has at least one published row (any locale) | | `published` | `has-published-version-document` | Published rows whose document has at least one draft row (any locale) | | `published` | `published-without-draft` | Published rows with no draft sibling for the same pair | -| `draft` | `published-without-draft` | Empty (degenerate) | +| `draft` | `published-without-draft` | Empty | | `published` | `published-with-draft` | Published rows that have a draft sibling for the same pair | -| `draft` | `published-with-draft` | Empty (degenerate) | +| `draft` | `published-with-draft` | Empty | +:::note Valid but empty combinations do not return validation errors. +::: ## Query never-published drafts {#never-published} +Return draft rows for `(documentId, locale)` pairs with no published version for that locale: + ```js const documents = await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', @@ -3887,10 +3891,10 @@ const documents = await strapi.documents('api::restaurant.restaurant').findMany( }); ``` -Returns draft rows for `(documentId, locale)` pairs with no published version for that locale. - ## Query has-published-version drafts {#has-published-version} +Return draft rows where a published row also exists for the same `(documentId, locale)`. Orphan published-only pairs are excluded: + ```js const documents = await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', @@ -3898,10 +3902,10 @@ const documents = await strapi.documents('api::restaurant.restaurant').findMany( }); ``` -Returns draft rows where a published row also exists for the same `(documentId, locale)`. Does not return draft rows for pairs that only exist as orphan published rows. - ## Query modified or unmodified documents {#modified-unmodified} +Compare `updatedAt` on the draft and published rows for the same pair: + ```js // Draft side of modified pairs await strapi.documents('api::restaurant.restaurant').findMany({ @@ -3916,10 +3920,10 @@ await strapi.documents('api::restaurant.restaurant').findMany({ }); ``` -Comparison uses `updatedAt` on the draft and published rows for the same pair. - ## Query document-scoped cohorts {#document-scoped} +Return draft rows for documents that have never been published in any locale: + ```js await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', @@ -3927,7 +3931,9 @@ await strapi.documents('api::restaurant.restaurant').findMany({ }); ``` -Returns draft rows for documents that have **never** been published in any locale. A multi-locale document with one published locale is excluded entirely, including its draft-only locales. +A multi-locale document with one published locale is excluded entirely, including its draft-only locales. + +Return draft rows for documents that have at least one published row in any locale: ```js await strapi.documents('api::restaurant.restaurant').findMany({ @@ -3936,10 +3942,12 @@ await strapi.documents('api::restaurant.restaurant').findMany({ }); ``` -Returns draft rows for documents that have at least one published row in any locale (broader than pair-scoped `has-published-version`). +This is broader than pair-scoped `has-published-version`. ## Query published rows without or with a draft peer {#published-slice} +`published-without-draft` and `published-with-draft` partition published rows per `(documentId, locale)` (excluding pairs with no published row): + ```js // Orphan published rows (published row, no draft sibling for the same pair) await strapi.documents('api::restaurant.restaurant').findMany({ @@ -3954,11 +3962,9 @@ await strapi.documents('api::restaurant.restaurant').findMany({ }); ``` -`published-without-draft` and `published-with-draft` partition published rows per `(documentId, locale)` (excluding pairs with no published row). - ## Use with `findOne()` and `findFirst()` {#find-one-find-first} -`publicationFilter` applies the same cohort rules. If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists. +`publicationFilter` applies the same cohort rules. If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: ```js await strapi.documents('api::restaurant.restaurant').findOne({ @@ -3974,6 +3980,8 @@ await strapi.documents('api::restaurant.restaurant').findOne({ ## Count documents in a cohort {#count} +Count draft rows in the never-published cohort: + ```js const neverPublishedCount = await strapi .documents('api::restaurant.restaurant') @@ -4852,7 +4860,7 @@ Source: https://docs.strapi.io/cms/api/rest/publication-filter # REST API: `publicationFilter` -The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. It selects derived publication cohorts while [`status`](/cms/api/rest/status) selects draft or published rows. +The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. Use it to query derived publication cohorts such as never-published or modified documents. The [`status`](/cms/api/rest/status) parameter still selects whether each matching document returns its draft or published row. :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. @@ -4872,43 +4880,39 @@ When `status` is omitted, the REST API defaults to `status=published` **before** The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status). +:::note Cohort definitions, the full `status` × `publicationFilter` matrix, Content Manager mapping, and validation rules are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). +::: -The REST API accepts the same kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. - -Invalid values return HTTP `400`. +Accepted kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. Invalid values return HTTP `400`. ## Get never-published draft documents {#never-published} -`GET /api/restaurants?status=draft&publicationFilter=never-published` +Pair-scoped `never-published` only matches draft rows. Pass `status=draft` because REST defaults to `status=published`.
JavaScript query (built with the qs library): -## Get modified documents (default published slice) {#modified} - -With no `status` parameter, REST returns **published** rows in the modified cohort: - -`GET /api/restaurants?publicationFilter=modified` - -To get **draft** rows instead, add `status=draft`: +## Get modified documents {#modified} -`GET /api/restaurants?status=draft&publicationFilter=modified` +The `modified` cohort includes pairs where the draft row is newer than its published peer. With no `status` parameter, REST returns **published** rows from that cohort. Pass `status=draft` to return the draft rows instead.
JavaScript query (built with the qs library): -## Get published rows without a draft peer {#published-without-draft} + -`GET /api/restaurants?status=published&publicationFilter=published-without-draft` +## Get published rows without a draft peer {#published-without-draft} -Because REST defaults to `status=published`, `?publicationFilter=published-without-draft` alone is equivalent. +The `published-without-draft` cohort matches published rows that have no draft sibling for the same `(documentId, locale)`. Because REST defaults to `status=published`, you can omit `status` in the query URL.
JavaScript query (built with the qs library): + + ## Combine with other parameters {#combine} `publicationFilter` can be combined with [`filters`](/cms/api/rest/filters), [`locale`](/cms/api/rest/locale), [`populate`](/cms/api/rest/populate-select), and other [REST parameters](/cms/api/rest/parameters). All conditions are applied together. @@ -4924,7 +4928,7 @@ The boolean `hasPublishedVersion` query parameter is deprecated. Accepted values Example: `GET /api/restaurants?status=draft&hasPublishedVersion=false` -If both `publicationFilter` and `hasPublishedVersion` are sent, `publicationFilter` wins. +If both `publicationFilter` and `hasPublishedVersion` are sent, `publicationFilter` takes precedence. Prefer `publicationFilter` for new integrations. @@ -9726,7 +9730,7 @@ The Draft & Publish feature allows to manage drafts for your content. -On the back-end server of Strapi, the Document Service API can also be used to interact with localized content: +On the back-end server of Strapi, the Document Service API can also query and manage draft and published content: From f77bb32306d878deb1bb6dbcf513fd00553dea9f Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:59:20 +0200 Subject: [PATCH 03/75] Convert REST publicationFilter examples to the redesigned Endpoint component The page still used the pre-redesign ApiCall/Request/Response markup with a collapsible 'JavaScript query (qs library)' details block, unlike its sibling REST pages. Rebuild the three examples as Endpoint blocks with REST and JavaScript code tabs and a responses panel, matching sort-pagination.md and the rest of the redesigned REST reference. --- .../docs/cms/api/rest/publication-filter.md | 211 ++++++++---------- 1 file changed, 91 insertions(+), 120 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 0007449f2c..3d668960c2 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -16,8 +16,6 @@ tags: - status --- -import QsForQueryBody from '/docs/snippets/qs-for-query-body.md' - # REST API: `publicationFilter` The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. Use it to query derived publication cohorts such as never-published or modified documents. The [`status`](/cms/api/rest/status) parameter still selects whether each matching document returns its draft or published row. @@ -50,40 +48,33 @@ Accepted kebab-case values: `never-published`, `has-published-version`, `modifie Pair-scoped `never-published` only matches draft rows. Pass `status=draft` because REST defaults to `status=published`. - - - - -`GET /api/restaurants?status=draft&publicationFilter=never-published` - - - -
-JavaScript query (built with the qs library): - - - -```js -const qs = require('qs'); -const query = qs.stringify( - { - status: 'draft', - publicationFilter: 'never-published', - }, - { - encodeValuesOnly: true, - } -); - -await request(`/api/restaurants?${query}`); -``` - -
- - - -```json {6} -{ + - -
+}` + } + ]} +/> ## Get modified documents {#modified} The `modified` cohort includes pairs where the draft row is newer than its published peer. With no `status` parameter, REST returns **published** rows from that cohort. Pass `status=draft` to return the draft rows instead. - - - - -`GET /api/restaurants?publicationFilter=modified` - - - -
-JavaScript query (built with the qs library): - - - -```js -const qs = require('qs'); -const query = qs.stringify( - { - publicationFilter: 'modified', - }, - { - encodeValuesOnly: true, - } -); - -await request(`/api/restaurants?${query}`); -``` - -
- - - -```json {6} -{ + - -
+}` + } + ]} +/> ## Get published rows without a draft peer {#published-without-draft} The `published-without-draft` cohort matches published rows that have no draft sibling for the same `(documentId, locale)`. Because REST defaults to `status=published`, you can omit `status` in the query URL. - - - - -`GET /api/restaurants?publicationFilter=published-without-draft` - - - -
-JavaScript query (built with the qs library): - - - -```js -const qs = require('qs'); -const query = qs.stringify( - { - publicationFilter: 'published-without-draft', - }, - { - encodeValuesOnly: true, - } -); - -await request(`/api/restaurants?${query}`); -``` - -
- - - -```json {6} -{ + - -
+}` + } + ]} +/> ## Combine with other parameters {#combine} From 87d3855d9f7e7f814eb7e3790836653d89909128 Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Wed, 1 Jul 2026 10:05:18 +0200 Subject: [PATCH 04/75] =?UTF-8?q?Split=20status=20=C3=97=20publicationFilt?= =?UTF-8?q?er=20matrix=20into=20draft=20and=20published=20tables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses PR review feedback: separate tables per status are easier to scan than one combined matrix. --- .../document-service/publication-filter.md | 43 ++++++++++-------- .../docs/cms/api/rest/publication-filter.md | 2 +- docusaurus/static/llms-full.txt | 45 +++++++++++-------- 3 files changed, 52 insertions(+), 38 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 69c7ac56c7..3fc5a80dde 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -91,24 +91,31 @@ The **Draft (never published)** filter is document-scoped (`never-published-docu ## Combine `status` and `publicationFilter` {#status-combination} -| `status` | `publicationFilter` | Rows returned | -| -------- | ------------------- | ------------- | -| `draft` | `never-published` | Draft rows for pairs never published in that locale | -| `published` | `never-published` | Empty | -| `draft` | `has-published-version` | Draft rows for pairs that also have a published version | -| `published` | `has-published-version` | Published rows for pairs that also have a draft version (excludes orphan published-only pairs) | -| `draft` | `modified` | Draft rows newer than their published peer | -| `published` | `modified` | Published rows whose draft peer is newer | -| `draft` | `unmodified` | Draft rows not newer than their published peer | -| `published` | `unmodified` | Published rows whose draft peer is not newer | -| `draft` | `never-published-document` | Draft rows whose document has no published row in any locale | -| `published` | `never-published-document` | Empty | -| `draft` | `has-published-version-document` | Draft rows whose document has at least one published row (any locale) | -| `published` | `has-published-version-document` | Published rows whose document has at least one draft row (any locale) | -| `published` | `published-without-draft` | Published rows with no draft sibling for the same pair | -| `draft` | `published-without-draft` | Empty | -| `published` | `published-with-draft` | Published rows that have a draft sibling for the same pair | -| `draft` | `published-with-draft` | Empty | +Pass `status` explicitly or rely on the [default for your API surface](#default-status). Each table lists which rows a `publicationFilter` returns for that `status`. + +### With `status: 'draft'` + +| `publicationFilter` | Rows returned | +| ------------------- | ------------- | +| `never-published` | Draft rows for pairs never published in that locale | +| `has-published-version` | Draft rows for pairs that also have a published version | +| `modified` | Draft rows newer than their published peer | +| `unmodified` | Draft rows not newer than their published peer | +| `never-published-document` | Draft rows whose document has no published row in any locale | +| `has-published-version-document` | Draft rows whose document has at least one published row (any locale) | +| `published-without-draft`, `published-with-draft` | No rows | + +### With `status: 'published'` + +| `publicationFilter` | Rows returned | +| ------------------- | ------------- | +| `has-published-version` | Published rows for pairs that also have a draft version (excludes orphan published-only pairs) | +| `modified` | Published rows whose draft peer is newer | +| `unmodified` | Published rows whose draft peer is not newer | +| `has-published-version-document` | Published rows whose document has at least one draft row (any locale) | +| `published-without-draft` | Published rows with no draft sibling for the same pair | +| `published-with-draft` | Published rows that have a draft sibling for the same pair | +| `never-published`, `never-published-document` | No rows | :::note Valid but empty combinations do not return validation errors. diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 3d668960c2..39f15f3eb4 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -39,7 +39,7 @@ When `status` is omitted, the REST API defaults to `status=published` **before** The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status). :::note -Cohort definitions, the full `status` × `publicationFilter` matrix, Content Manager mapping, and validation rules are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). +Cohort definitions, `status` and `publicationFilter` combination tables, Content Manager mapping, and validation rules are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). ::: Accepted kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. Invalid values return HTTP `400`. diff --git a/docusaurus/static/llms-full.txt b/docusaurus/static/llms-full.txt index 8da332c8a3..d71e158ff9 100644 --- a/docusaurus/static/llms-full.txt +++ b/docusaurus/static/llms-full.txt @@ -7132,24 +7132,31 @@ The **Draft (never published)** filter is document-scoped (`never-published-docu ## Combine `status` and `publicationFilter` {#status-combination} -| `status` | `publicationFilter` | Rows returned | -| -------- | ------------------- | ------------- | -| `draft` | `never-published` | Draft rows for pairs never published in that locale | -| `published` | `never-published` | Empty | -| `draft` | `has-published-version` | Draft rows for pairs that also have a published version | -| `published` | `has-published-version` | Published rows for pairs that also have a draft version (excludes orphan published-only pairs) | -| `draft` | `modified` | Draft rows newer than their published peer | -| `published` | `modified` | Published rows whose draft peer is newer | -| `draft` | `unmodified` | Draft rows not newer than their published peer | -| `published` | `unmodified` | Published rows whose draft peer is not newer | -| `draft` | `never-published-document` | Draft rows whose document has no published row in any locale | -| `published` | `never-published-document` | Empty | -| `draft` | `has-published-version-document` | Draft rows whose document has at least one published row (any locale) | -| `published` | `has-published-version-document` | Published rows whose document has at least one draft row (any locale) | -| `published` | `published-without-draft` | Published rows with no draft sibling for the same pair | -| `draft` | `published-without-draft` | Empty | -| `published` | `published-with-draft` | Published rows that have a draft sibling for the same pair | -| `draft` | `published-with-draft` | Empty | +Pass `status` explicitly or rely on the [default for your API surface](#default-status). Each table lists which rows a `publicationFilter` returns for that `status`. + +### With `status: 'draft'` + +| `publicationFilter` | Rows returned | +| ------------------- | ------------- | +| `never-published` | Draft rows for pairs never published in that locale | +| `has-published-version` | Draft rows for pairs that also have a published version | +| `modified` | Draft rows newer than their published peer | +| `unmodified` | Draft rows not newer than their published peer | +| `never-published-document` | Draft rows whose document has no published row in any locale | +| `has-published-version-document` | Draft rows whose document has at least one published row (any locale) | +| `published-without-draft`, `published-with-draft` | No rows | + +### With `status: 'published'` + +| `publicationFilter` | Rows returned | +| ------------------- | ------------- | +| `has-published-version` | Published rows for pairs that also have a draft version (excludes orphan published-only pairs) | +| `modified` | Published rows whose draft peer is newer | +| `unmodified` | Published rows whose draft peer is not newer | +| `has-published-version-document` | Published rows whose document has at least one draft row (any locale) | +| `published-without-draft` | Published rows with no draft sibling for the same pair | +| `published-with-draft` | Published rows that have a draft sibling for the same pair | +| `never-published`, `never-published-document` | No rows | :::note Valid but empty combinations do not return validation errors. @@ -14172,7 +14179,7 @@ When `status` is omitted, the REST API defaults to `status=published` **before** The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status). :::note -Cohort definitions, the full `status` × `publicationFilter` matrix, Content Manager mapping, and validation rules are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). +Cohort definitions, `status` and `publicationFilter` combination tables, Content Manager mapping, and validation rules are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). ::: Accepted kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. Invalid values return HTTP `400`. From 42f6d4d6a65cf89445658f96de92af1fbb8bba17 Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Wed, 1 Jul 2026 10:06:28 +0200 Subject: [PATCH 05/75] Remove hasPublishedVersion from publicationFilter documentation The parameter was never officially documented; omit deprecated references from REST, GraphQL, and Document Service API pages. --- .../document-service/publication-filter.md | 13 --- docusaurus/docs/cms/api/graphql.md | 4 - .../docs/cms/api/rest/publication-filter.md | 15 --- docusaurus/static/llms-full.txt | 105 ++++++++++-------- 4 files changed, 61 insertions(+), 76 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 3fc5a80dde..afa858d3b5 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -242,19 +242,6 @@ Unknown `publicationFilter` values are rejected: - REST API: returns HTTP `400`. - GraphQL: invalid enum values fail at query validation. -## Deprecated `hasPublishedVersion` parameter {#has-published-version-deprecated} - -The boolean `hasPublishedVersion` parameter is deprecated in favor of `publicationFilter`. Strapi still accepts it on the REST API, GraphQL, and Document Service API and maps it to **document-scoped** modes: - -| `hasPublishedVersion` | Maps to | -| --------------------- | ------- | -| `false` (or string `'false'`) | `never-published-document` | -| `true` (or string `'true'`) | `has-published-version-document` | - -If both `publicationFilter` and `hasPublishedVersion` are passed, `publicationFilter` takes precedence. - -REST and GraphQL examples: [REST API: `publicationFilter`](/cms/api/rest/publication-filter#has-published-version-deprecated), [GraphQL API: `publicationFilter`](/cms/api/graphql#publication-filter). - ## Why not filter on `publishedAt` alone? {#why-not-published-at} A single row's `publishedAt` only describes that row. Cohorts such as `never-published`, `has-published-version`, and `modified` require comparing or correlating **two rows** for the same `(documentId, locale)`. `publicationFilter` encodes those rules in one server-side query instead of multiple client round-trips. diff --git a/docusaurus/docs/cms/api/graphql.md b/docusaurus/docs/cms/api/graphql.md index 6e8545304b..edf18f1d98 100644 --- a/docusaurus/docs/cms/api/graphql.md +++ b/docusaurus/docs/cms/api/graphql.md @@ -485,10 +485,6 @@ Available enum values: | `PUBLISHED_WITHOUT_DRAFT` | `published-without-draft` | | `PUBLISHED_WITH_DRAFT` | `published-with-draft` | -:::note -The deprecated `hasPublishedVersion` boolean argument is still accepted (`true` / `false`) and maps to `NEVER_PUBLISHED_DOCUMENT` / `HAS_PUBLISHED_VERSION_DOCUMENT`. If both `publicationFilter` and `hasPublishedVersion` are provided, `publicationFilter` takes precedence. Prefer `publicationFilter` for new queries. -::: - ## Mutations Mutations in GraphQL are used to modify data (e.g. create, update, and delete data). diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 39f15f3eb4..d6adc177e6 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -201,18 +201,3 @@ await request(\`/api/restaurants?\${query}\`);` ## Combine with other parameters {#combine} `publicationFilter` can be combined with [`filters`](/cms/api/rest/filters), [`locale`](/cms/api/rest/locale), [`populate`](/cms/api/rest/populate-select), and other [REST parameters](/cms/api/rest/parameters). All conditions are applied together. - -## Deprecated `hasPublishedVersion` parameter {#has-published-version-deprecated} - -The boolean `hasPublishedVersion` query parameter is deprecated. Accepted values: `true`, `false`, `'true'`, or `'false'`. Strapi maps it to document-scoped `publicationFilter` values: - -| `hasPublishedVersion` | Maps to | -| --------------------- | ------- | -| `false` / `'false'` | `never-published-document` | -| `true` / `'true'` | `has-published-version-document` | - -Example: `GET /api/restaurants?status=draft&hasPublishedVersion=false` - -If both `publicationFilter` and `hasPublishedVersion` are sent, `publicationFilter` takes precedence. - -Prefer `publicationFilter` for new integrations. diff --git a/docusaurus/static/llms-full.txt b/docusaurus/static/llms-full.txt index d71e158ff9..8555ab68f6 100644 --- a/docusaurus/static/llms-full.txt +++ b/docusaurus/static/llms-full.txt @@ -7283,19 +7283,6 @@ Unknown `publicationFilter` values are rejected: - REST API: returns HTTP `400`. - GraphQL: invalid enum values fail at query validation. -## Deprecated `hasPublishedVersion` parameter {#has-published-version-deprecated} - -The boolean `hasPublishedVersion` parameter is deprecated in favor of `publicationFilter`. Strapi still accepts it on the REST API, GraphQL, and Document Service API and maps it to **document-scoped** modes: - -| `hasPublishedVersion` | Maps to | -| --------------------- | ------- | -| `false` (or string `'false'`) | `never-published-document` | -| `true` (or string `'true'`) | `has-published-version-document` | - -If both `publicationFilter` and `hasPublishedVersion` are passed, `publicationFilter` takes precedence. - -REST and GraphQL examples: [REST API: `publicationFilter`](/cms/api/rest/publication-filter#has-published-version-deprecated), [GraphQL API: `publicationFilter`](/cms/api/graphql#publication-filter). - ## Why not filter on `publishedAt` alone? {#why-not-published-at} A single row's `publishedAt` only describes that row. Cohorts such as `never-published`, `has-published-version`, and `modified` require comparing or correlating **two rows** for the same `(documentId, locale)`. `publicationFilter` encodes those rules in one server-side query instead of multiple client round-trips. @@ -8953,10 +8940,6 @@ Available enum values: | `PUBLISHED_WITHOUT_DRAFT` | `published-without-draft` | | `PUBLISHED_WITH_DRAFT` | `published-with-draft` | -:::note -The deprecated `hasPublishedVersion` boolean argument is still accepted (`true` / `false`) and maps to `NEVER_PUBLISHED_DOCUMENT` / `HAS_PUBLISHED_VERSION_DOCUMENT`. If both `publicationFilter` and `hasPublishedVersion` are provided, `publicationFilter` takes precedence. Prefer `publicationFilter` for new queries. -::: - ## Mutations Mutations in GraphQL are used to modify data (e.g. create, update, and delete data). @@ -14188,11 +14171,28 @@ Accepted kebab-case values: `never-published`, `has-published-version`, `modifie Pair-scoped `never-published` only matches draft rows. Pass `status=draft` because REST defaults to `status=published`. -**Get draft restaurants that have never been published for their locale:** -`GET /api/restaurants?status=draft&publicationFilter=never-published` +#### GET /api/restaurants?status=draft&publicationFilter=never-published — Get draft restaurants that have never been published for their locale -**Example response:** -```json {6} +**REST:** +``` +GET /api/restaurants?status=draft&publicationFilter=never-published +``` + +**JavaScript:** +``` +const qs = require('qs'); +const query = qs.stringify({ + status: 'draft', + publicationFilter: 'never-published', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(\`/api/restaurants?\${query}\`); +``` + +**Response 200 OK:** +```json { "data": [ { @@ -14217,11 +14217,27 @@ Pair-scoped `never-published` only matches draft rows. Pass `status=draft` becau The `modified` cohort includes pairs where the draft row is newer than its published peer. With no `status` parameter, REST returns **published** rows from that cohort. Pass `status=draft` to return the draft rows instead. -**Get published restaurants in the modified cohort (default status):** -`GET /api/restaurants?publicationFilter=modified` +#### GET /api/restaurants?publicationFilter=modified — Get published restaurants in the modified cohort (default status) -**Example response:** -```json {6} +**REST:** +``` +GET /api/restaurants?publicationFilter=modified +``` + +**JavaScript:** +``` +const qs = require('qs'); +const query = qs.stringify({ + publicationFilter: 'modified', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(\`/api/restaurants?\${query}\`); +``` + +**Response 200 OK:** +```json { "data": [ { @@ -14246,11 +14262,27 @@ The `modified` cohort includes pairs where the draft row is newer than its publi The `published-without-draft` cohort matches published rows that have no draft sibling for the same `(documentId, locale)`. Because REST defaults to `status=published`, you can omit `status` in the query URL. -**Get published restaurants with no draft row for the same locale:** -`GET /api/restaurants?publicationFilter=published-without-draft` +#### GET /api/restaurants?publicationFilter=published-without-draft — Get published restaurants with no draft row for the same locale -**Example response:** -```json {6} +**REST:** +``` +GET /api/restaurants?publicationFilter=published-without-draft +``` + +**JavaScript:** +``` +const qs = require('qs'); +const query = qs.stringify({ + publicationFilter: 'published-without-draft', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(\`/api/restaurants?\${query}\`); +``` + +**Response 200 OK:** +```json { "data": [ { @@ -14275,21 +14307,6 @@ The `published-without-draft` cohort matches published rows that have no draft s `publicationFilter` can be combined with [`filters`](/cms/api/rest/filters), [`locale`](/cms/api/rest/locale), [`populate`](/cms/api/rest/populate-select), and other [REST parameters](/cms/api/rest/parameters). All conditions are applied together. -## Deprecated `hasPublishedVersion` parameter {#has-published-version-deprecated} - -The boolean `hasPublishedVersion` query parameter is deprecated. Accepted values: `true`, `false`, `'true'`, or `'false'`. Strapi maps it to document-scoped `publicationFilter` values: - -| `hasPublishedVersion` | Maps to | -| --------------------- | ------- | -| `false` / `'false'` | `never-published-document` | -| `true` / `'true'` | `has-published-version-document` | - -Example: `GET /api/restaurants?status=draft&hasPublishedVersion=false` - -If both `publicationFilter` and `hasPublishedVersion` are sent, `publicationFilter` takes precedence. - -Prefer `publicationFilter` for new integrations. - # Relations From 57fa9950894d4f8998c8413d38e619ab00c752fb Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Wed, 1 Jul 2026 10:10:49 +0200 Subject: [PATCH 06/75] Trim publicationFilter docs for clarity Consolidate redundant example sections, fold validation and CM notes into the intro, and lighten GraphQL examples while keeping reference tables and findOne/count guidance. --- .../document-service/publication-filter.md | 102 +++--------- docusaurus/docs/cms/api/graphql.md | 12 -- .../docs/cms/api/rest/publication-filter.md | 2 +- docusaurus/static/llms-code.txt | 152 +++++++----------- docusaurus/static/llms-full.txt | 116 +++---------- 5 files changed, 97 insertions(+), 287 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index afa858d3b5..0881404f06 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -20,13 +20,15 @@ tags: The [`status`](/cms/api/document-service/status) parameter selects which **row slice** to read for each document: `draft` rows have `publishedAt: null`, and `published` rows have a non-null `publishedAt`. -The optional `publicationFilter` parameter selects a **derived publication cohort** first: a set of `(documentId, locale)` pairs (or `documentId` only when [Internationalization (i18n)](/cms/features/internationalization) is disabled) defined by how draft and published rows relate. Strapi then returns the row that matches both the cohort and the resolved `status`. +The optional `publicationFilter` parameter selects a **derived publication cohort** first: a set of `(documentId, locale)` pairs (or `documentId` only when [Internationalization (i18n)](/cms/features/internationalization) is disabled) defined by how draft and published rows relate. Cohorts compare draft and published rows for the same document; a single row's `publishedAt` is not enough. Strapi then returns the row that matches both the cohort and the resolved `status`. :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. ::: -`publicationFilter` is supported on `findOne()`, `findFirst()`, `findMany()`, and `count()`. It can be combined with [`filters`](/cms/api/document-service/filters), [`populate`](/cms/api/document-service/populate), and other query parameters. Invalid values raise a validation error. +`publicationFilter` is supported on `findOne()`, `findFirst()`, `findMany()`, and `count()`. It is combined with other query parameters as a logical AND, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same cohort logic. Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). + +In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not pair-scoped `never-published`). Other Status filter options use internal APIs, not public `publicationFilter` parameters. ## Default `status` when `publicationFilter` is used {#default-status} @@ -68,26 +70,9 @@ REST and the Document Service API use kebab-case strings. GraphQL exposes the sa For content-types without i18n, read `(documentId, locale)` as `documentId` only. -### Semantics notes {#semantics} - -- **`has-published-version` excludes orphan published rows**: If only a published row exists for a pair (no draft sibling), that pair is **not** in the `has-published-version` cohort. Orphan published rows can appear under `published-without-draft` when querying with `status: 'published'`. -- **`modified` / `unmodified` require both slices**: Pairs with only a draft or only a published row are not included. -- **`modified` ∪ `unmodified` = `has-published-version`** (for the same `status`): The two modes partition pairs that have both slices. -- **Document-scoped modes**: Existence checks use `documentId` only. A document with draft EN + published NL qualifies for `has-published-version-document` even though EN is never published at the pair level. -- **Published-slice diagnostics** (`published-without-draft`, `published-with-draft`): Only select published rows. They return no rows when `status` is `'draft'`. - -### Content Manager list filters {#content-manager} - -The Content Manager **Status** filter (`__status`) is translated server-side. Only the **Draft (never published)** option uses `publicationFilter`: - -| Content Manager filter | Document Service query equivalent | -| ---------------------- | --------------------------------- | -| Draft (never published) | `status: 'draft'`, `publicationFilter: 'never-published-document'` | -| Published (all) | `status: 'published'` (no `publicationFilter`) | -| Published (modified) | Internal `publicationStatusFilter` (not a public REST/GraphQL parameter); similar intent to `status: 'published'` + `publicationFilter: 'modified'` but implemented separately in the Content Manager API | -| Published (unmodified) | Internal `publicationStatusFilter` (not a public REST/GraphQL parameter) | - -The **Draft (never published)** filter is document-scoped (`never-published-document`), not pair-scoped `never-published`. +:::note +`has-published-version` excludes orphan published rows (published-only pairs with no draft sibling). Those pairs match `published-without-draft` when `status` is `'published'`. +::: ## Combine `status` and `publicationFilter` {#status-combination} @@ -121,51 +106,33 @@ Pass `status` explicitly or rely on the [default for your API surface](#default- Valid but empty combinations do not return validation errors. ::: -## Query never-published drafts {#never-published} +## Examples {#examples} -Return draft rows for `(documentId, locale)` pairs with no published version for that locale: +### Never-published and modified cohorts {#never-published} ```js -const documents = await strapi.documents('api::restaurant.restaurant').findMany({ +// Pair-scoped: drafts never published in this locale +await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'never-published', }); -``` - -## Query has-published-version drafts {#has-published-version} - -Return draft rows where a published row also exists for the same `(documentId, locale)`. Orphan published-only pairs are excluded: - -```js -const documents = await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'has-published-version', -}); -``` - -## Query modified or unmodified documents {#modified-unmodified} -Compare `updatedAt` on the draft and published rows for the same pair: - -```js -// Draft side of modified pairs +// Modified pairs: draft side vs published side await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'modified', }); -// Published side of unmodified pairs await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', - publicationFilter: 'unmodified', + publicationFilter: 'modified', }); ``` -## Query document-scoped cohorts {#document-scoped} - -Return draft rows for documents that have never been published in any locale: +### Document-scoped cohorts {#document-scoped} ```js +// Documents with no published row in any locale await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'never-published-document', @@ -174,29 +141,16 @@ await strapi.documents('api::restaurant.restaurant').findMany({ A multi-locale document with one published locale is excluded entirely, including its draft-only locales. -Return draft rows for documents that have at least one published row in any locale: - -```js -await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'has-published-version-document', -}); -``` - -This is broader than pair-scoped `has-published-version`. - -## Query published rows without or with a draft peer {#published-slice} +### Published rows with or without a draft peer {#published-slice} -`published-without-draft` and `published-with-draft` partition published rows per `(documentId, locale)` (excluding pairs with no published row): +`published-without-draft` and `published-with-draft` require `status: 'published'`. ```js -// Orphan published rows (published row, no draft sibling for the same pair) await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'published-without-draft', }); -// Published rows that still have a draft sibling await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'published-with-draft', @@ -205,7 +159,7 @@ await strapi.documents('api::restaurant.restaurant').findMany({ ## Use with `findOne()` and `findFirst()` {#find-one-find-first} -`publicationFilter` applies the same cohort rules. If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: +If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: ```js await strapi.documents('api::restaurant.restaurant').findOne({ @@ -215,13 +169,9 @@ await strapi.documents('api::restaurant.restaurant').findOne({ }); ``` -## Combine with `filters` and `populate` {#filters-populate} - -`publicationFilter` is merged with other query filters (logical AND). When [populating relations](/cms/api/document-service/populate), nested queries on draft & publish content-types inherit the same cohort logic so populated results stay consistent with the parent query. - ## Count documents in a cohort {#count} -Count draft rows in the never-published cohort: +Without `publicationFilter`, `count({ status: 'draft' })` still counts every draft row, including drafts whose document already has a published version. Use `publicationFilter: 'never-published'` or `'never-published-document'` to count only never-published cohorts (see [`status` documentation](/cms/api/document-service/status#count)). ```js const neverPublishedCount = await strapi @@ -231,17 +181,3 @@ const neverPublishedCount = await strapi publicationFilter: 'never-published', }); ``` - -Without `publicationFilter`, `count({ status: 'draft' })` still counts every draft row, including drafts whose document already has a published version. Use `publicationFilter: 'never-published'` or `'never-published-document'` to count only never-published cohorts (see [`status` documentation](/cms/api/document-service/status#count)). - -## Validation {#validation} - -Unknown `publicationFilter` values are rejected: - -- Document Service API: throws a validation error. -- REST API: returns HTTP `400`. -- GraphQL: invalid enum values fail at query validation. - -## Why not filter on `publishedAt` alone? {#why-not-published-at} - -A single row's `publishedAt` only describes that row. Cohorts such as `never-published`, `has-published-version`, and `modified` require comparing or correlating **two rows** for the same `(documentId, locale)`. `publicationFilter` encodes those rules in one server-side query instead of multiple client round-trips. diff --git a/docusaurus/docs/cms/api/graphql.md b/docusaurus/docs/cms/api/graphql.md index edf18f1d98..502cce8fa3 100644 --- a/docusaurus/docs/cms/api/graphql.md +++ b/docusaurus/docs/cms/api/graphql.md @@ -440,8 +440,6 @@ Combine `publicationFilter` with `status` the same way as for REST (see [Documen When `status` is omitted, GraphQL defaults to `PUBLISHED` before applying `publicationFilter` (same as REST). Example: `restaurants(publicationFilter: MODIFIED)` returns published rows in the modified cohort; use `status: DRAFT` to return draft rows instead. -Built-in root queries (for example `restaurants`, `restaurants_connection`) pass `publicationFilter` down to populated draft & publish relations on nested fields so relation results match the parent query cohort. - ```graphql title="Example: Fetch never-published draft documents" query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { restaurants(status: DRAFT, publicationFilter: NEVER_PUBLISHED) { @@ -452,16 +450,6 @@ query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { } ``` -```graphql title="Example: Fetch published rows without a draft peer" -query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { - restaurants(status: PUBLISHED, publicationFilter: PUBLISHED_WITHOUT_DRAFT) { - documentId - name - publishedAt - } -} -``` - ```graphql title="Example: Modified cohort with default PUBLISHED status" query Query { restaurants(publicationFilter: MODIFIED) { diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index d6adc177e6..2846e13d29 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -39,7 +39,7 @@ When `status` is omitted, the REST API defaults to `status=published` **before** The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status). :::note -Cohort definitions, `status` and `publicationFilter` combination tables, Content Manager mapping, and validation rules are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). +Cohort definitions and `status` / `publicationFilter` combination tables are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). ::: Accepted kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. Invalid values return HTTP `400`. diff --git a/docusaurus/static/llms-code.txt b/docusaurus/static/llms-code.txt index 43c82d9f55..00a13bff22 100644 --- a/docusaurus/static/llms-code.txt +++ b/docusaurus/static/llms-code.txt @@ -4580,98 +4580,62 @@ await strapi.documents('api::restaurant.restaurant').findMany({ ``` -## Query never-published drafts -Description: Return draft rows for (documentId, locale) pairs with no published version for that locale: +## Never-published and modified cohorts +Description: :::note Valid but empty combinations do not return validation errors. (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#never-published) Language: JavaScript File path: N/A ```js -const documents = await strapi.documents('api::restaurant.restaurant').findMany({ +// Pair-scoped: drafts never published in this locale +await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'never-published', }); -``` - - -## Query has-published-version drafts -Description: Return draft rows where a published row also exists for the same (documentId, locale). -(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#has-published-version) - -Language: JavaScript -File path: N/A - -```js -const documents = await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'has-published-version', -}); -``` - -## Query modified or unmodified documents -Description: Compare updatedAt on the draft and published rows for the same pair: -(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#modified-unmodified) - -Language: JavaScript -File path: N/A - -```js -// Draft side of modified pairs +// Modified pairs: draft side vs published side await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'modified', }); -// Published side of unmodified pairs await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', - publicationFilter: 'unmodified', + publicationFilter: 'modified', }); ``` -## Query document-scoped cohorts -Description: Return draft rows for documents that have never been published in any locale: +## Document-scoped cohorts +Description: :::note Valid but empty combinations do not return validation errors. (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#document-scoped) Language: JavaScript File path: N/A ```js +// Documents with no published row in any locale await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'never-published-document', }); ``` -Language: JavaScript -File path: N/A -```js -await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'has-published-version-document', -}); -``` - - -## Query published rows without or with a draft peer -Description: published-without-draft and published-with-draft partition published rows per (documentId, locale) (excluding pairs with no published row): +## Published rows with or without a draft peer +Description: published-without-draft and published-with-draft require status: 'published'. (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#published-slice) Language: JavaScript File path: N/A ```js -// Orphan published rows (published row, no draft sibling for the same pair) await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'published-without-draft', }); -// Published rows that still have a draft sibling await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'published-with-draft', @@ -4680,7 +4644,7 @@ await strapi.documents('api::restaurant.restaurant').findMany({ ## Use with findOne() and findFirst() -Description: publicationFilter applies the same cohort rules. +Description: If the requested document (and locale, when applicable) is not in the cohort, findOne() and findFirst() return null even when the documentId exists: (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#find-one-find-first) Language: JavaScript @@ -4696,7 +4660,7 @@ await strapi.documents('api::restaurant.restaurant').findOne({ ## Count documents in a cohort -Description: Count draft rows in the never-published cohort: +Description: Without publicationFilter, count({ status: 'draft' }) still counts every draft row, including drafts whose document already has a published version. (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#count) Language: JavaScript @@ -6207,7 +6171,7 @@ query Query($status: PublicationStatus) { ## Filter by derived publication cohort -Description: Built-in root queries (for example restaurants, restaurants_connection) pass publicationFilter down to populated draft & publish relations on nested fields so relation results match the parent query cohort. +Description: When status is omitted, GraphQL defaults to PUBLISHED before applying publicationFilter (same as REST). (Source: https://docs.strapi.io/cms/api/graphql#publication-filter) Language: GRAPHQL @@ -6227,20 +6191,6 @@ query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { Language: GRAPHQL File path: Example: -```graphql -query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { - restaurants(status: PUBLISHED, publicationFilter: PUBLISHED_WITHOUT_DRAFT) { - documentId - name - publishedAt - } -} -``` - ---- -Language: GRAPHQL -File path: Example: - ```graphql query Query { restaurants(publicationFilter: MODIFIED) { @@ -10213,28 +10163,32 @@ await request(`/api/articles?${query}`); Source: https://docs.strapi.io/cms/api/rest/publication-filter ## Get never-published draft documents -Description: Code example from "Get never-published draft documents" +Description: GET /api/restaurants?status=draft&publicationFilter=never-published — Get draft restaurants that have never been published for their locale (Source: https://docs.strapi.io/cms/api/rest/publication-filter#never-published) +Language: Bash +File path: N/A + +```bash +GET /api/restaurants?status=draft&publicationFilter=never-published +``` + +--- Language: JavaScript File path: N/A ```js const qs = require('qs'); -const query = qs.stringify( - { - status: 'draft', - publicationFilter: 'never-published', - }, - { - encodeValuesOnly: true, - } -); +const query = qs.stringify({ + status: 'draft', + publicationFilter: 'never-published', +}, { + encodeValuesOnly: true, // prettify URL +}); await request(`/api/restaurants?${query}`); ``` ---- Language: JSON File path: N/A @@ -10261,27 +10215,31 @@ File path: N/A ## Get modified documents -Description: Code example from "Get modified documents" +Description: GET /api/restaurants?publicationFilter=modified — Get published restaurants in the modified cohort (default status) (Source: https://docs.strapi.io/cms/api/rest/publication-filter#modified) +Language: Bash +File path: N/A + +```bash +GET /api/restaurants?publicationFilter=modified +``` + +--- Language: JavaScript File path: N/A ```js const qs = require('qs'); -const query = qs.stringify( - { - publicationFilter: 'modified', - }, - { - encodeValuesOnly: true, - } -); +const query = qs.stringify({ + publicationFilter: 'modified', +}, { + encodeValuesOnly: true, // prettify URL +}); await request(`/api/restaurants?${query}`); ``` ---- Language: JSON File path: N/A @@ -10308,27 +10266,31 @@ File path: N/A ## Get published rows without a draft peer -Description: Code example from "Get published rows without a draft peer" +Description: GET /api/restaurants?publicationFilter=published-without-draft — Get published restaurants with no draft row for the same locale (Source: https://docs.strapi.io/cms/api/rest/publication-filter#published-without-draft) +Language: Bash +File path: N/A + +```bash +GET /api/restaurants?publicationFilter=published-without-draft +``` + +--- Language: JavaScript File path: N/A ```js const qs = require('qs'); -const query = qs.stringify( - { - publicationFilter: 'published-without-draft', - }, - { - encodeValuesOnly: true, - } -); +const query = qs.stringify({ + publicationFilter: 'published-without-draft', +}, { + encodeValuesOnly: true, // prettify URL +}); await request(`/api/restaurants?${query}`); ``` ---- Language: JSON File path: N/A diff --git a/docusaurus/static/llms-full.txt b/docusaurus/static/llms-full.txt index 8555ab68f6..6a732e178e 100644 --- a/docusaurus/static/llms-full.txt +++ b/docusaurus/static/llms-full.txt @@ -7061,13 +7061,15 @@ Source: https://docs.strapi.io/cms/api/document-service/publication-filter The [`status`](/cms/api/document-service/status) parameter selects which **row slice** to read for each document: `draft` rows have `publishedAt: null`, and `published` rows have a non-null `publishedAt`. -The optional `publicationFilter` parameter selects a **derived publication cohort** first: a set of `(documentId, locale)` pairs (or `documentId` only when [Internationalization (i18n)](/cms/features/internationalization) is disabled) defined by how draft and published rows relate. Strapi then returns the row that matches both the cohort and the resolved `status`. +The optional `publicationFilter` parameter selects a **derived publication cohort** first: a set of `(documentId, locale)` pairs (or `documentId` only when [Internationalization (i18n)](/cms/features/internationalization) is disabled) defined by how draft and published rows relate. Cohorts compare draft and published rows for the same document; a single row's `publishedAt` is not enough. Strapi then returns the row that matches both the cohort and the resolved `status`. :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. ::: -`publicationFilter` is supported on `findOne()`, `findFirst()`, `findMany()`, and `count()`. It can be combined with [`filters`](/cms/api/document-service/filters), [`populate`](/cms/api/document-service/populate), and other query parameters. Invalid values raise a validation error. +`publicationFilter` is supported on `findOne()`, `findFirst()`, `findMany()`, and `count()`. It is combined with other query parameters as a logical AND, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same cohort logic. Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). + +In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not pair-scoped `never-published`). Other Status filter options use internal APIs, not public `publicationFilter` parameters. ## Default `status` when `publicationFilter` is used {#default-status} @@ -7109,26 +7111,9 @@ REST and the Document Service API use kebab-case strings. GraphQL exposes the sa For content-types without i18n, read `(documentId, locale)` as `documentId` only. -### Semantics notes {#semantics} - -- **`has-published-version` excludes orphan published rows**: If only a published row exists for a pair (no draft sibling), that pair is **not** in the `has-published-version` cohort. Orphan published rows can appear under `published-without-draft` when querying with `status: 'published'`. -- **`modified` / `unmodified` require both slices**: Pairs with only a draft or only a published row are not included. -- **`modified` ∪ `unmodified` = `has-published-version`** (for the same `status`): The two modes partition pairs that have both slices. -- **Document-scoped modes**: Existence checks use `documentId` only. A document with draft EN + published NL qualifies for `has-published-version-document` even though EN is never published at the pair level. -- **Published-slice diagnostics** (`published-without-draft`, `published-with-draft`): Only select published rows. They return no rows when `status` is `'draft'`. - -### Content Manager list filters {#content-manager} - -The Content Manager **Status** filter (`__status`) is translated server-side. Only the **Draft (never published)** option uses `publicationFilter`: - -| Content Manager filter | Document Service query equivalent | -| ---------------------- | --------------------------------- | -| Draft (never published) | `status: 'draft'`, `publicationFilter: 'never-published-document'` | -| Published (all) | `status: 'published'` (no `publicationFilter`) | -| Published (modified) | Internal `publicationStatusFilter` (not a public REST/GraphQL parameter); similar intent to `status: 'published'` + `publicationFilter: 'modified'` but implemented separately in the Content Manager API | -| Published (unmodified) | Internal `publicationStatusFilter` (not a public REST/GraphQL parameter) | - -The **Draft (never published)** filter is document-scoped (`never-published-document`), not pair-scoped `never-published`. +:::note +`has-published-version` excludes orphan published rows (published-only pairs with no draft sibling). Those pairs match `published-without-draft` when `status` is `'published'`. +::: ## Combine `status` and `publicationFilter` {#status-combination} @@ -7162,51 +7147,33 @@ Pass `status` explicitly or rely on the [default for your API surface](#default- Valid but empty combinations do not return validation errors. ::: -## Query never-published drafts {#never-published} +## Examples {#examples} -Return draft rows for `(documentId, locale)` pairs with no published version for that locale: +### Never-published and modified cohorts {#never-published} ```js -const documents = await strapi.documents('api::restaurant.restaurant').findMany({ +// Pair-scoped: drafts never published in this locale +await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'never-published', }); -``` -## Query has-published-version drafts {#has-published-version} - -Return draft rows where a published row also exists for the same `(documentId, locale)`. Orphan published-only pairs are excluded: - -```js -const documents = await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'has-published-version', -}); -``` - -## Query modified or unmodified documents {#modified-unmodified} - -Compare `updatedAt` on the draft and published rows for the same pair: - -```js -// Draft side of modified pairs +// Modified pairs: draft side vs published side await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'modified', }); -// Published side of unmodified pairs await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', - publicationFilter: 'unmodified', + publicationFilter: 'modified', }); ``` -## Query document-scoped cohorts {#document-scoped} - -Return draft rows for documents that have never been published in any locale: +### Document-scoped cohorts {#document-scoped} ```js +// Documents with no published row in any locale await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'never-published-document', @@ -7215,29 +7182,16 @@ await strapi.documents('api::restaurant.restaurant').findMany({ A multi-locale document with one published locale is excluded entirely, including its draft-only locales. -Return draft rows for documents that have at least one published row in any locale: +### Published rows with or without a draft peer {#published-slice} -```js -await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'has-published-version-document', -}); -``` - -This is broader than pair-scoped `has-published-version`. - -## Query published rows without or with a draft peer {#published-slice} - -`published-without-draft` and `published-with-draft` partition published rows per `(documentId, locale)` (excluding pairs with no published row): +`published-without-draft` and `published-with-draft` require `status: 'published'`. ```js -// Orphan published rows (published row, no draft sibling for the same pair) await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'published-without-draft', }); -// Published rows that still have a draft sibling await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'published-with-draft', @@ -7246,7 +7200,7 @@ await strapi.documents('api::restaurant.restaurant').findMany({ ## Use with `findOne()` and `findFirst()` {#find-one-find-first} -`publicationFilter` applies the same cohort rules. If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: +If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: ```js await strapi.documents('api::restaurant.restaurant').findOne({ @@ -7256,13 +7210,9 @@ await strapi.documents('api::restaurant.restaurant').findOne({ }); ``` -## Combine with `filters` and `populate` {#filters-populate} - -`publicationFilter` is merged with other query filters (logical AND). When [populating relations](/cms/api/document-service/populate), nested queries on draft & publish content-types inherit the same cohort logic so populated results stay consistent with the parent query. - ## Count documents in a cohort {#count} -Count draft rows in the never-published cohort: +Without `publicationFilter`, `count({ status: 'draft' })` still counts every draft row, including drafts whose document already has a published version. Use `publicationFilter: 'never-published'` or `'never-published-document'` to count only never-published cohorts (see [`status` documentation](/cms/api/document-service/status#count)). ```js const neverPublishedCount = await strapi @@ -7273,20 +7223,6 @@ const neverPublishedCount = await strapi }); ``` -Without `publicationFilter`, `count({ status: 'draft' })` still counts every draft row, including drafts whose document already has a published version. Use `publicationFilter: 'never-published'` or `'never-published-document'` to count only never-published cohorts (see [`status` documentation](/cms/api/document-service/status#count)). - -## Validation {#validation} - -Unknown `publicationFilter` values are rejected: - -- Document Service API: throws a validation error. -- REST API: returns HTTP `400`. -- GraphQL: invalid enum values fail at query validation. - -## Why not filter on `publishedAt` alone? {#why-not-published-at} - -A single row's `publishedAt` only describes that row. Cohorts such as `never-published`, `has-published-version`, and `modified` require comparing or correlating **two rows** for the same `(documentId, locale)`. `publicationFilter` encodes those rules in one server-side query instead of multiple client round-trips. - # Using Sort & Pagination with the Document Service API @@ -8895,8 +8831,6 @@ Combine `publicationFilter` with `status` the same way as for REST (see [Documen When `status` is omitted, GraphQL defaults to `PUBLISHED` before applying `publicationFilter` (same as REST). Example: `restaurants(publicationFilter: MODIFIED)` returns published rows in the modified cohort; use `status: DRAFT` to return draft rows instead. -Built-in root queries (for example `restaurants`, `restaurants_connection`) pass `publicationFilter` down to populated draft & publish relations on nested fields so relation results match the parent query cohort. - ```graphql title="Example: Fetch never-published draft documents" query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { restaurants(status: DRAFT, publicationFilter: NEVER_PUBLISHED) { @@ -8907,16 +8841,6 @@ query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { } ``` -```graphql title="Example: Fetch published rows without a draft peer" -query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { - restaurants(status: PUBLISHED, publicationFilter: PUBLISHED_WITHOUT_DRAFT) { - documentId - name - publishedAt - } -} -``` - ```graphql title="Example: Modified cohort with default PUBLISHED status" query Query { restaurants(publicationFilter: MODIFIED) { @@ -14162,7 +14086,7 @@ When `status` is omitted, the REST API defaults to `status=published` **before** The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status). :::note -Cohort definitions, `status` and `publicationFilter` combination tables, Content Manager mapping, and validation rules are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). +Cohort definitions and `status` / `publicationFilter` combination tables are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). ::: Accepted kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. Invalid values return HTTP `400`. From e3cc08364a65fadf97e52b5f51111b9b52de14e2 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:06:32 +0200 Subject: [PATCH 07/75] Restructure Document Service publicationFilter page for readability --- .../document-service/publication-filter.md | 196 +++++++++++------- 1 file changed, 121 insertions(+), 75 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 0881404f06..4828163ddc 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -18,146 +18,122 @@ tags: # Document Service API: `publicationFilter` -The [`status`](/cms/api/document-service/status) parameter selects which **row slice** to read for each document: `draft` rows have `publishedAt: null`, and `published` rows have a non-null `publishedAt`. + -The optional `publicationFilter` parameter selects a **derived publication cohort** first: a set of `(documentId, locale)` pairs (or `documentId` only when [Internationalization (i18n)](/cms/features/internationalization) is disabled) defined by how draft and published rows relate. Cohorts compare draft and published rows for the same document; a single row's `publishedAt` is not enough. Strapi then returns the row that matches both the cohort and the resolved `status`. +Use the optional `publicationFilter` parameter to query documents by the relationship between their draft and published versions, for example drafts that were never published, or entries modified since they were last published. It works with `findOne()`, `findFirst()`, `findMany()`, and `count()`, and combines with other query parameters. `status` still decides whether you get the draft or the published row. + + + +The [`status`](/cms/api/document-service/status) parameter answers "draft or published?". The `publicationFilter` parameter answers a different question: "which documents, based on how their draft and published versions relate?". + +For example, you can ask for drafts that were never published, or for entries whose draft has unsaved changes compared to what is live. `publicationFilter` selects that group of documents first; `status` then decides which row (draft or published) is returned for each. :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. ::: -`publicationFilter` is supported on `findOne()`, `findFirst()`, `findMany()`, and `count()`. It is combined with other query parameters as a logical AND, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same cohort logic. Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). - -In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not pair-scoped `never-published`). Other Status filter options use internal APIs, not public `publicationFilter` parameters. +## Quick example {#quick-example} -## Default `status` when `publicationFilter` is used {#default-status} - -`publicationFilter` is applied **after** `status` is resolved (explicitly or by default). Defaults differ by API surface: - -| API surface | Default `status` when omitted | -| ----------- | ----------------------------- | -| Document Service API (direct) | `'draft'` | -| [REST API](/cms/api/rest/publication-filter) | `'published'` | -| [GraphQL API](/cms/api/graphql#publication-filter) | `PUBLISHED` | - -The following example compares Document Service and REST behavior when only `publicationFilter: 'modified'` is passed: +To read the drafts that have never been published, pass `status: 'draft'` (so you read the draft row) and `publicationFilter: 'never-published'` (so you only keep documents with no published version): ```js -// Document Service API → draft rows in the modified cohort await strapi.documents('api::restaurant.restaurant').findMany({ - publicationFilter: 'modified', + status: 'draft', + publicationFilter: 'never-published', }); - -// REST: GET /api/restaurants?publicationFilter=modified → published rows in the modified cohort ``` -Pair-scoped modes such as `never-published` only include draft rows in the cohort. With REST or GraphQL defaults (`status=published`), those queries return an empty result set unless you pass `status=draft` / `status: DRAFT`. +The rest of this page lists [all available values](#values), explains [how these cohorts are defined](#how-cohorts-work), and shows [more examples](#examples). ## Available values {#values} -REST and the Document Service API use kebab-case strings. GraphQL exposes the same cohorts through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). +`publicationFilter` accepts one of the following kebab-case values. REST and the Document Service API use these strings directly; GraphQL exposes the same set through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). -| Value | Scope | Cohort definition (which `(documentId, locale)` pairs match) | -| ----- | ----- | -------------------------------------------------------------- | -| `never-published` | Pair | No row with non-null `publishedAt` exists for the same `(documentId, locale)` | -| `has-published-version` | Pair | **Both** a draft row and a published row exist for the same `(documentId, locale)` | -| `modified` | Pair | Both slices exist and `draft.updatedAt > published.updatedAt` | -| `unmodified` | Pair | Both slices exist and `draft.updatedAt <= published.updatedAt` | -| `never-published-document` | Document | No row with non-null `publishedAt` exists for the same `documentId` in **any** locale | -| `has-published-version-document` | Document | At least one published row exists for the same `documentId` in **any** locale | -| `published-without-draft` | Pair | A published row exists for the pair and **no** draft row exists for the same `(documentId, locale)` | -| `published-with-draft` | Pair | A published row exists for the pair and a draft row **also** exists for the same `(documentId, locale)` | +| Value | What it selects | Scope | +| ----- | --------------- | ----- | +| `never-published` | Documents never published in that locale | Pair | +| `has-published-version` | Documents that have both a draft and a published version | Pair | +| `modified` | Documents whose draft was edited since it was last published | Pair | +| `unmodified` | Documents whose draft has not changed since it was last published | Pair | +| `published-without-draft` | Published entries with no matching draft | Pair | +| `published-with-draft` | Published entries that also have a matching draft | Pair | +| `never-published-document` | Documents never published in any locale | Document | +| `has-published-version-document` | Documents published in at least one locale | Document | -For content-types without i18n, read `(documentId, locale)` as `documentId` only. +Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). -:::note -`has-published-version` excludes orphan published rows (published-only pairs with no draft sibling). Those pairs match `published-without-draft` when `status` is `'published'`. -::: +The `Scope` column matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: a *Pair* value looks at 1 locale at a time, while a *Document* value looks at the document across all its locales. See [How cohorts work](#how-cohorts-work) for the precise definitions. Without i18n, the two scopes behave the same. -## Combine `status` and `publicationFilter` {#status-combination} +## Default `status` per API surface {#default-status} -Pass `status` explicitly or rely on the [default for your API surface](#default-status). Each table lists which rows a `publicationFilter` returns for that `status`. +`publicationFilter` is applied *after* `status` is resolved, so the default `status` of your API surface affects what you get back: -### With `status: 'draft'` +| API surface | Default `status` when omitted | +| ----------- | ----------------------------- | +| Document Service API (direct) | `draft` | +| [REST API](/cms/api/rest/publication-filter) | `published` | +| [GraphQL API](/cms/api/graphql#publication-filter) | `PUBLISHED` | -| `publicationFilter` | Rows returned | -| ------------------- | ------------- | -| `never-published` | Draft rows for pairs never published in that locale | -| `has-published-version` | Draft rows for pairs that also have a published version | -| `modified` | Draft rows newer than their published peer | -| `unmodified` | Draft rows not newer than their published peer | -| `never-published-document` | Draft rows whose document has no published row in any locale | -| `has-published-version-document` | Draft rows whose document has at least one published row (any locale) | -| `published-without-draft`, `published-with-draft` | No rows | +This matters for pair-scoped values that only contain draft rows, such as `never-published`: with the REST or GraphQL defaults (`published`), the query returns an empty result set unless you explicitly pass `status=draft` / `status: DRAFT`. The Document Service defaults to `draft`, so the [quick example](#quick-example) above works without setting `status`. -### With `status: 'published'` +## More examples {#examples} -| `publicationFilter` | Rows returned | -| ------------------- | ------------- | -| `has-published-version` | Published rows for pairs that also have a draft version (excludes orphan published-only pairs) | -| `modified` | Published rows whose draft peer is newer | -| `unmodified` | Published rows whose draft peer is not newer | -| `has-published-version-document` | Published rows whose document has at least one draft row (any locale) | -| `published-without-draft` | Published rows with no draft sibling for the same pair | -| `published-with-draft` | Published rows that have a draft sibling for the same pair | -| `never-published`, `never-published-document` | No rows | +The following examples show the most common cohorts. For the exact rows each combination returns, see [How cohorts work](#how-cohorts-work). -:::note -Valid but empty combinations do not return validation errors. -::: - -## Examples {#examples} +### Never-published and modified documents {#never-published} -### Never-published and modified cohorts {#never-published} +Both values below are pair-scoped, so with i18n they consider one locale at a time: ```js -// Pair-scoped: drafts never published in this locale +// Drafts never published in this locale await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'never-published', }); -// Modified pairs: draft side vs published side +// Draft rows whose draft is newer than the published version await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'modified', }); +// Published rows whose draft is newer (the currently-live version of modified entries) await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'modified', }); ``` -### Document-scoped cohorts {#document-scoped} +### Documents never published in any locale {#document-scoped} + +`never-published-document` is document-scoped, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: ```js -// Documents with no published row in any locale await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'never-published-document', }); ``` -A multi-locale document with one published locale is excluded entirely, including its draft-only locales. +### Published entries with or without a draft {#published-slice} -### Published rows with or without a draft peer {#published-slice} - -`published-without-draft` and `published-with-draft` require `status: 'published'`. +`published-without-draft` and `published-with-draft` describe published rows, so they require `status: 'published'`: ```js +// Published entries with no matching draft await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'published-without-draft', }); +// Published entries that also have a matching draft await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'published-with-draft', }); ``` -## Use with `findOne()` and `findFirst()` {#find-one-find-first} +### Use with `findOne()` and `findFirst()` {#find-one-find-first} If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: @@ -169,9 +145,9 @@ await strapi.documents('api::restaurant.restaurant').findOne({ }); ``` -## Count documents in a cohort {#count} +### Count documents in a cohort {#count} -Without `publicationFilter`, `count({ status: 'draft' })` still counts every draft row, including drafts whose document already has a published version. Use `publicationFilter: 'never-published'` or `'never-published-document'` to count only never-published cohorts (see [`status` documentation](/cms/api/document-service/status#count)). +Without `publicationFilter`, `count({ status: 'draft' })` counts every draft row, including drafts whose document already has a published version. Add `publicationFilter` to count only a specific cohort (see the [`status` documentation](/cms/api/document-service/status#count)): ```js const neverPublishedCount = await strapi @@ -181,3 +157,73 @@ const neverPublishedCount = await strapi publicationFilter: 'never-published', }); ``` + +## How cohorts work {#how-cohorts-work} + +This section explains the model behind the values above. You do not need it to use the common cohorts, but it is what lets you predict the result of any `status` × `publicationFilter` combination. + +### The model {#the-model} + +For any document, Strapi can store 2 rows for the same `(documentId, locale)` pair: + +- a *draft row*, with `publishedAt: null` +- a *published row*, with a non-null `publishedAt` + +The [`status`](/cms/api/document-service/status) parameter picks which of these 2 rows to read. + +`publicationFilter` does something `status` alone cannot: it groups documents by *how the draft and published rows relate to each other*. A single row's `publishedAt` is not enough to answer questions like "is this draft newer than what is live?" or "was this document ever published?", because those questions compare 2 rows. That group of matching documents is called a *cohort*. + +Strapi resolves the cohort first, then returns the row that matches the resolved `status`. Values ending in `-document` compare rows across every locale of a document; the others compare rows within a single `(documentId, locale)` pair (read this as `documentId` only when i18n is disabled). + +### Cohort definitions {#cohort-definitions} + +| Value | Scope | A `(documentId, locale)` pair matches when… | +| ----- | ----- | -------------------------------------------- | +| `never-published` | Pair | no row with a non-null `publishedAt` exists for the pair | +| `has-published-version` | Pair | both a draft row and a published row exist for the pair | +| `modified` | Pair | both rows exist and `draft.updatedAt > published.updatedAt` | +| `unmodified` | Pair | both rows exist and `draft.updatedAt <= published.updatedAt` | +| `published-without-draft` | Pair | a published row exists for the pair and no draft row exists | +| `published-with-draft` | Pair | a published row exists for the pair and a draft row also exists | +| `never-published-document` | Document | no row with a non-null `publishedAt` exists for the `documentId` in any locale | +| `has-published-version-document` | Document | at least one published row exists for the `documentId` in any locale | + +:::note +`has-published-version` excludes orphan published rows (a published row with no draft sibling for the same pair). Those rows match `published-without-draft` instead when `status` is `published`. +::: + +### Which rows a combination returns {#status-combination} + +Because the cohort is resolved before `status` selects a row, some combinations always return nothing. The grid below shows which `status` × `publicationFilter` pairs return rows and which are always empty: + +| `publicationFilter` | With `status: 'draft'` | With `status: 'published'` | +| ------------------- | :--------------------: | :------------------------: | +| `never-published` | ✅ | ∅ | +| `never-published-document` | ✅ | ∅ | +| `has-published-version` | ✅ | ✅ | +| `has-published-version-document` | ✅ | ✅ | +| `modified` | ✅ | ✅ | +| `unmodified` | ✅ | ✅ | +| `published-without-draft` | ∅ | ✅ | +| `published-with-draft` | ∅ | ✅ | + +✅ returns the rows of the resolved status within the cohort; ∅ is always empty because the cohort has no row of that status. + +When a combination returns rows, it returns them as follows: + +- With `status: 'draft'`, you get the draft rows of documents in the cohort. +- With `status: 'published'`, you get the published rows of documents in the cohort. + +For example, `modified` with `status: 'draft'` returns the newer draft rows, while `modified` with `status: 'published'` returns the currently-live published rows of those same documents. + +:::note +Valid but empty combinations (the ∅ cells) do not return validation errors, they return no rows. +::: + +## Combine with other parameters {#combine} + +`publicationFilter` is combined with other query parameters as a logical `AND`, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same cohort logic. + +## Content Manager mapping {#content-manager} + +In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the pair-scoped `never-published`). Other Status filter options use internal APIs rather than public `publicationFilter` values. From d45ad948fc59f6d66ad64ce406e5f9318561553c Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:06:32 +0200 Subject: [PATCH 08/75] Restructure REST publicationFilter page for readability --- .../docs/cms/api/rest/publication-filter.md | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 2846e13d29..15980cdef9 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -18,35 +18,23 @@ tags: # REST API: `publicationFilter` -The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. Use it to query derived publication cohorts such as never-published or modified documents. The [`status`](/cms/api/rest/status) parameter still selects whether each matching document returns its draft or published row. + -:::prerequisites -The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. -::: - -## Default `status` {#default-status} +Add the optional `publicationFilter` query parameter to filter results by the relationship between a document's draft and published versions, for example never-published or modified entries. The [`status`](/cms/api/rest/status) parameter still selects whether each result returns its draft or published row. REST defaults to `status=published`, so pass `status=draft` for draft-only cohorts. -When `status` is omitted, the REST API defaults to `status=published` **before** applying `publicationFilter`. + -| Query | Effective behavior | -| ----- | ------------------ | -| `?publicationFilter=never-published` | Empty (cohort is draft-only; default status is `published`) | -| `?status=draft&publicationFilter=never-published` | Never-published draft rows | -| `?publicationFilter=modified` | Published rows in the modified cohort | -| `?status=draft&publicationFilter=modified` | Draft rows in the modified cohort | -| `?publicationFilter=published-without-draft` | Orphan published rows (default `status=published` is correct) | +The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. A *cohort* is a set of documents grouped by how their draft and published versions relate, for example documents never published, or documents whose draft was edited since it was last published. `publicationFilter` selects the cohort; [`status`](/cms/api/rest/status) then selects whether each result returns its draft or published row. -The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status). +This page shows how to query the most common cohorts over REST. For the full list of values, their exact definitions, and every `status` combination, see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter), which is the reference for the underlying model. -:::note -Cohort definitions and `status` / `publicationFilter` combination tables are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). +:::prerequisites +The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. ::: -Accepted kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. Invalid values return HTTP `400`. - ## Get never-published draft documents {#never-published} -Pair-scoped `never-published` only matches draft rows. Pass `status=draft` because REST defaults to `status=published`. +`never-published` matches documents with no published version for that locale, so only draft rows exist. Pass `status=draft`, because REST defaults to `status=published`: -## Get published rows without a draft peer {#published-without-draft} +## Get published documents without a draft {#published-without-draft} -The `published-without-draft` cohort matches published rows that have no draft sibling for the same `(documentId, locale)`. Because REST defaults to `status=published`, you can omit `status` in the query URL. +The `published-without-draft` cohort matches published rows that have no draft for the same `(documentId, locale)`. REST defaults to `status=published`, so you can omit `status`: +## Understand the default `status` {#default-status} + +When `status` is omitted, REST defaults to `status=published` **before** applying `publicationFilter`. This is the most common source of unexpected empty results: a draft-only cohort such as `never-published` returns nothing under the default status. The table below shows the effect for the values used above: + +| Query | Result | +| ----- | ------ | +| `?publicationFilter=never-published` | Empty (this cohort has draft rows only, and the default status is `published`) | +| `?status=draft&publicationFilter=never-published` | Never-published draft rows | +| `?publicationFilter=modified` | Published rows of modified documents | +| `?status=draft&publicationFilter=modified` | Draft rows of modified documents | +| `?publicationFilter=published-without-draft` | Published rows with no draft (default `status=published` is correct) | + +The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status) for the full comparison across API surfaces. + +## Reference: accepted values {#values} + +REST accepts these kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. Invalid values return HTTP `400`. + +Each value, its scope (pair or document), and its exact cohort definition are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter#values). + ## Combine with other parameters {#combine} `publicationFilter` can be combined with [`filters`](/cms/api/rest/filters), [`locale`](/cms/api/rest/locale), [`populate`](/cms/api/rest/populate-select), and other [REST parameters](/cms/api/rest/parameters). All conditions are applied together. From c2c4147909e78c867460e8bcd9cc37993b049b8f Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:19:58 +0200 Subject: [PATCH 09/75] Rename Scope column to Scope (i18n) in publicationFilter values tables --- .../docs/cms/api/document-service/publication-filter.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 4828163ddc..cb5acf9e5b 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -49,7 +49,7 @@ The rest of this page lists [all available values](#values), explains [how these `publicationFilter` accepts one of the following kebab-case values. REST and the Document Service API use these strings directly; GraphQL exposes the same set through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). -| Value | What it selects | Scope | +| Value | What it selects | Scope (i18n) | | ----- | --------------- | ----- | | `never-published` | Documents never published in that locale | Pair | | `has-published-version` | Documents that have both a draft and a published version | Pair | @@ -62,7 +62,7 @@ The rest of this page lists [all available values](#values), explains [how these Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). -The `Scope` column matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: a *Pair* value looks at 1 locale at a time, while a *Document* value looks at the document across all its locales. See [How cohorts work](#how-cohorts-work) for the precise definitions. Without i18n, the two scopes behave the same. +The `Scope (i18n)` column matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: a *Pair* value looks at 1 locale at a time, while a *Document* value looks at the document across all its locales. See [How cohorts work](#how-cohorts-work) for the precise definitions. Without i18n, the two scopes behave the same. ## Default `status` per API surface {#default-status} @@ -177,7 +177,7 @@ Strapi resolves the cohort first, then returns the row that matches the resolved ### Cohort definitions {#cohort-definitions} -| Value | Scope | A `(documentId, locale)` pair matches when… | +| Value | Scope (i18n) | A `(documentId, locale)` pair matches when… | | ----- | ----- | -------------------------------------------- | | `never-published` | Pair | no row with a non-null `publishedAt` exists for the pair | | `has-published-version` | Pair | both a draft row and a published row exist for the pair | From 2958810039698624bb8e52b1b9d149c6805b718f Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:31:23 +0200 Subject: [PATCH 10/75] Add Key terms box defining row, cohort, and scope on Document Service page --- .../document-service/publication-filter.md | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index cb5acf9e5b..5d76dd77e7 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -28,6 +28,24 @@ The [`status`](/cms/api/document-service/status) parameter answers "draft or pub For example, you can ask for drafts that were never published, or for entries whose draft has unsaved changes compared to what is live. `publicationFilter` selects that group of documents first; `status` then decides which row (draft or published) is returned for each. +:::note Key terms +Strapi Draft & Publish stores each entry as up to 2 database rows for the same document and locale: + +- a *draft row* (`publishedAt` is empty) +- a *published row* (`publishedAt` is set) + +The `status` parameter picks which of the 2 rows to read. + +`publicationFilter` instead selects a *cohort*: a group of documents defined by how their draft and published rows relate (for example, never published, or draft newer than published). Some of these questions compare the 2 rows, so they cannot be expressed by filtering on `publishedAt` alone. That is what `publicationFilter` is for. + +Each cohort has a *scope*, shown in the values table below: + +- a *pair-scoped* value looks at 1 locale at a time (a `documentId` + `locale` pair). For example, `never-published`. +- a *document-scoped* value looks at the whole document across all locales. For example, `never-published-document`. + +Without i18n, both scopes behave the same. +::: + :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. ::: @@ -62,7 +80,9 @@ The rest of this page lists [all available values](#values), explains [how these Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). -The `Scope (i18n)` column matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: a *Pair* value looks at 1 locale at a time, while a *Document* value looks at the document across all its locales. See [How cohorts work](#how-cohorts-work) for the precise definitions. Without i18n, the two scopes behave the same. +The `Scope (i18n)` column matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: a *Pair* value looks at 1 locale at a time, while a *Document* value looks at the document across all its locales. See [How cohorts work](#how-cohorts-work) for the precise definitions. Without i18n, the 2 scopes behave the same. + +`published-without-draft` and `published-with-draft` describe published rows, so they only return results with `status: 'published'`. ## Default `status` per API surface {#default-status} @@ -160,18 +180,11 @@ const neverPublishedCount = await strapi ## How cohorts work {#how-cohorts-work} -This section explains the model behind the values above. You do not need it to use the common cohorts, but it is what lets you predict the result of any `status` × `publicationFilter` combination. +This section gives the precise definitions behind the values above (the Key terms box near the top of the page introduces the same concepts in plain language). You do not need it to use the common cohorts, but it is what lets you predict the result of any `status` × `publicationFilter` combination. ### The model {#the-model} -For any document, Strapi can store 2 rows for the same `(documentId, locale)` pair: - -- a *draft row*, with `publishedAt: null` -- a *published row*, with a non-null `publishedAt` - -The [`status`](/cms/api/document-service/status) parameter picks which of these 2 rows to read. - -`publicationFilter` does something `status` alone cannot: it groups documents by *how the draft and published rows relate to each other*. A single row's `publishedAt` is not enough to answer questions like "is this draft newer than what is live?" or "was this document ever published?", because those questions compare 2 rows. That group of matching documents is called a *cohort*. +A document can have up to 2 rows for the same `(documentId, locale)` pair: a *draft row* (`publishedAt: null`) and a *published row* (non-null `publishedAt`). The [`status`](/cms/api/document-service/status) parameter picks which of these 2 rows to read, while `publicationFilter` selects the *cohort* of documents first, based on how their draft and published rows relate. Strapi resolves the cohort first, then returns the row that matches the resolved `status`. Values ending in `-document` compare rows across every locale of a document; the others compare rows within a single `(documentId, locale)` pair (read this as `documentId` only when i18n is disabled). From afe92bbf2c8bdefc104601055019fca1cc3063e3 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:31:24 +0200 Subject: [PATCH 11/75] Define row and pair terms in REST publicationFilter intro --- docusaurus/docs/cms/api/rest/publication-filter.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 15980cdef9..d4c5ff868b 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -24,7 +24,9 @@ Add the optional `publicationFilter` query parameter to filter results by the re -The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. A *cohort* is a set of documents grouped by how their draft and published versions relate, for example documents never published, or documents whose draft was edited since it was last published. `publicationFilter` selects the cohort; [`status`](/cms/api/rest/status) then selects whether each result returns its draft or published row. +The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. With Draft & Publish, each entry can have up to 2 rows for a locale: a *draft row* (`publishedAt` is empty) and a *published row* (`publishedAt` is set). + +A *cohort* is a set of documents grouped by how their draft and published rows relate, for example documents never published, or documents whose draft was edited since it was last published. You cannot get these groups by filtering on `publishedAt` yourself: questions like "was this ever published?" or "is the draft newer than the live version?" compare the 2 rows, not one. `publicationFilter` selects the cohort; [`status`](/cms/api/rest/status) then selects whether each result returns its draft or published row. This page shows how to query the most common cohorts over REST. For the full list of values, their exact definitions, and every `status` combination, see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter), which is the reference for the underlying model. @@ -204,7 +206,7 @@ The Document Service API defaults to `status=draft` instead. See [Document Servi REST accepts these kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. Invalid values return HTTP `400`. -Each value, its scope (pair or document), and its exact cohort definition are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter#values). +Each value, its scope (pair, meaning one `documentId` and locale at a time, or document, meaning across all locales), and its exact cohort definition are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter#values). ## Combine with other parameters {#combine} From 8fbc5ca4e6dce672154b5a9e5708e0d2dbc03b24 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:45:04 +0200 Subject: [PATCH 12/75] Fix garbled publicationFilter question in Document Service intro --- docusaurus/docs/cms/api/document-service/publication-filter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 5d76dd77e7..8264f2da59 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -24,7 +24,7 @@ Use the optional `publicationFilter` parameter to query documents by the relatio -The [`status`](/cms/api/document-service/status) parameter answers "draft or published?". The `publicationFilter` parameter answers a different question: "which documents, based on how their draft and published versions relate?". +The [`status`](/cms/api/document-service/status) parameter answers "do I want the draft or the published version?". The `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". For example, you can ask for drafts that were never published, or for entries whose draft has unsaved changes compared to what is live. `publicationFilter` selects that group of documents first; `status` then decides which row (draft or published) is returned for each. From 05842c876bfcfda8bd743c8e6921484b22bdf19a Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:38:18 +0200 Subject: [PATCH 13/75] Split Document Service publicationFilter page into explanation and reference Follows the Diataxis distinction: an Understand section explains the two-row model, scope, and per-surface status defaults; an API reference section lists values, exact definitions, the combination grid, and examples. Examples now use one Endpoint component per query, matching other Document Service pages. --- .../document-service/publication-filter.md | 407 +++++++++++++----- 1 file changed, 289 insertions(+), 118 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 8264f2da59..29cedd8deb 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -3,6 +3,7 @@ title: Using publicationFilter with the Document Service API description: Use the publicationFilter parameter with Strapi's Document Service API to query derived Draft & Publish cohorts such as never-published or modified documents. displayed_sidebar: cmsSidebar sidebar_label: Publication filter +toc_max_heading_level: 4 tags: - API - Content API @@ -36,14 +37,7 @@ Strapi Draft & Publish stores each entry as up to 2 database rows for the same d The `status` parameter picks which of the 2 rows to read. -`publicationFilter` instead selects a *cohort*: a group of documents defined by how their draft and published rows relate (for example, never published, or draft newer than published). Some of these questions compare the 2 rows, so they cannot be expressed by filtering on `publishedAt` alone. That is what `publicationFilter` is for. - -Each cohort has a *scope*, shown in the values table below: - -- a *pair-scoped* value looks at 1 locale at a time (a `documentId` + `locale` pair). For example, `never-published`. -- a *document-scoped* value looks at the whole document across all locales. For example, `never-published-document`. - -Without i18n, both scopes behave the same. +`publicationFilter` instead selects a *cohort*: a group of documents defined by how their draft and published rows relate (for example, never published, or draft newer than published). Some cohorts compare the 2 rows, so they cannot be expressed by filtering on `publishedAt` alone. ::: :::prerequisites @@ -54,37 +48,60 @@ The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled o To read the drafts that have never been published, pass `status: 'draft'` (so you read the draft row) and `publicationFilter: 'never-published'` (so you only keep documents with no published version): -```js -await strapi.documents('api::restaurant.restaurant').findMany({ + + +The rest of this page is organized in 2 parts: [Understand `status` vs `publicationFilter`](#understand) explains the model behind cohorts, and the [API reference](#reference) lists all values, their exact definitions, and more examples. You can use the reference directly and come back to the explanations when a combination surprises you. + +## Understand `status` vs `publicationFilter` {#understand} + +This section explains the model behind `publicationFilter`. You do not need it to use the common cohorts shown in the [examples](#examples), but it is what lets you predict the result of any `status` × `publicationFilter` combination. -The rest of this page lists [all available values](#values), explains [how these cohorts are defined](#how-cohorts-work), and shows [more examples](#examples). +### The model {#the-model} -## Available values {#values} +A document can have up to 2 rows for the same `(documentId, locale)` pair: a *draft row* (`publishedAt: null`) and a *published row* (non-null `publishedAt`). The [`status`](/cms/api/document-service/status) parameter picks which of these 2 rows to read, while `publicationFilter` selects the *cohort* of documents first, based on how their draft and published rows relate. -`publicationFilter` accepts one of the following kebab-case values. REST and the Document Service API use these strings directly; GraphQL exposes the same set through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). +Strapi resolves the cohort first, then returns the row that matches the resolved `status`. -| Value | What it selects | Scope (i18n) | -| ----- | --------------- | ----- | -| `never-published` | Documents never published in that locale | Pair | -| `has-published-version` | Documents that have both a draft and a published version | Pair | -| `modified` | Documents whose draft was edited since it was last published | Pair | -| `unmodified` | Documents whose draft has not changed since it was last published | Pair | -| `published-without-draft` | Published entries with no matching draft | Pair | -| `published-with-draft` | Published entries that also have a matching draft | Pair | -| `never-published-document` | Documents never published in any locale | Document | -| `has-published-version-document` | Documents published in at least one locale | Document | +### Scope: pair vs. document {#scope} -Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). +Each `publicationFilter` value has a *scope*, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: -The `Scope (i18n)` column matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: a *Pair* value looks at 1 locale at a time, while a *Document* value looks at the document across all its locales. See [How cohorts work](#how-cohorts-work) for the precise definitions. Without i18n, the 2 scopes behave the same. +- a *pair-scoped* value looks at 1 locale at a time (a `documentId` + `locale` pair). For example, `never-published` matches documents never published in that locale. +- a *document-scoped* value (its name ends in `-document`) looks at the whole document across all its locales. For example, `never-published-document` matches documents never published in any locale. -`published-without-draft` and `published-with-draft` describe published rows, so they only return results with `status: 'published'`. +Without i18n, both scopes behave the same. -## Default `status` per API surface {#default-status} +### Default `status` per API surface {#default-status} `publicationFilter` is applied *after* `status` is resolved, so the default `status` of your API surface affects what you get back: @@ -96,100 +113,33 @@ The `Scope (i18n)` column matters when [Internationalization (i18n)](/cms/featur This matters for pair-scoped values that only contain draft rows, such as `never-published`: with the REST or GraphQL defaults (`published`), the query returns an empty result set unless you explicitly pass `status=draft` / `status: DRAFT`. The Document Service defaults to `draft`, so the [quick example](#quick-example) above works without setting `status`. -## More examples {#examples} - -The following examples show the most common cohorts. For the exact rows each combination returns, see [How cohorts work](#how-cohorts-work). - -### Never-published and modified documents {#never-published} - -Both values below are pair-scoped, so with i18n they consider one locale at a time: - -```js -// Drafts never published in this locale -await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'never-published', -}); - -// Draft rows whose draft is newer than the published version -await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'modified', -}); - -// Published rows whose draft is newer (the currently-live version of modified entries) -await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'published', - publicationFilter: 'modified', -}); -``` - -### Documents never published in any locale {#document-scoped} - -`never-published-document` is document-scoped, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: - -```js -await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'never-published-document', -}); -``` - -### Published entries with or without a draft {#published-slice} - -`published-without-draft` and `published-with-draft` describe published rows, so they require `status: 'published'`: - -```js -// Published entries with no matching draft -await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'published', - publicationFilter: 'published-without-draft', -}); - -// Published entries that also have a matching draft -await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'published', - publicationFilter: 'published-with-draft', -}); -``` - -### Use with `findOne()` and `findFirst()` {#find-one-find-first} - -If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: +## API reference {#reference} -```js -await strapi.documents('api::restaurant.restaurant').findOne({ - documentId: 'a1b2c3d4e5f6g7h8i9j0klm', - status: 'draft', - publicationFilter: 'never-published', -}); -``` +This section lists the accepted values, their exact definitions, what each `status` × `publicationFilter` combination returns, and more examples. -### Count documents in a cohort {#count} +### Available values {#values} -Without `publicationFilter`, `count({ status: 'draft' })` counts every draft row, including drafts whose document already has a published version. Add `publicationFilter` to count only a specific cohort (see the [`status` documentation](/cms/api/document-service/status#count)): +`publicationFilter` accepts one of the following kebab-case values. REST and the Document Service API use these strings directly; GraphQL exposes the same set through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). The `Scope (i18n)` column refers to the [pair vs. document scope](#scope). -```js -const neverPublishedCount = await strapi - .documents('api::restaurant.restaurant') - .count({ - status: 'draft', - publicationFilter: 'never-published', - }); -``` - -## How cohorts work {#how-cohorts-work} - -This section gives the precise definitions behind the values above (the Key terms box near the top of the page introduces the same concepts in plain language). You do not need it to use the common cohorts, but it is what lets you predict the result of any `status` × `publicationFilter` combination. - -### The model {#the-model} +| Value | What it selects | Scope (i18n) | +| ----- | --------------- | ----- | +| `never-published` | Documents never published in that locale | Pair | +| `has-published-version` | Documents that have both a draft and a published version | Pair | +| `modified` | Documents whose draft was edited since it was last published | Pair | +| `unmodified` | Documents whose draft has not changed since it was last published | Pair | +| `published-without-draft` | Published entries with no matching draft | Pair | +| `published-with-draft` | Published entries that also have a matching draft | Pair | +| `never-published-document` | Documents never published in any locale | Document | +| `has-published-version-document` | Documents published in at least one locale | Document | -A document can have up to 2 rows for the same `(documentId, locale)` pair: a *draft row* (`publishedAt: null`) and a *published row* (non-null `publishedAt`). The [`status`](/cms/api/document-service/status) parameter picks which of these 2 rows to read, while `publicationFilter` selects the *cohort* of documents first, based on how their draft and published rows relate. +Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). -Strapi resolves the cohort first, then returns the row that matches the resolved `status`. Values ending in `-document` compare rows across every locale of a document; the others compare rows within a single `(documentId, locale)` pair (read this as `documentId` only when i18n is disabled). +`published-without-draft` and `published-with-draft` describe published rows, so they only return results with `status: 'published'`. ### Cohort definitions {#cohort-definitions} +The following table lists the same 8 values in the same order as [above](#values), this time with the exact predicate each one applies: + | Value | Scope (i18n) | A `(documentId, locale)` pair matches when… | | ----- | ----- | -------------------------------------------- | | `never-published` | Pair | no row with a non-null `publishedAt` exists for the pair | @@ -212,13 +162,13 @@ Because the cohort is resolved before `status` selects a row, some combinations | `publicationFilter` | With `status: 'draft'` | With `status: 'published'` | | ------------------- | :--------------------: | :------------------------: | | `never-published` | ✅ | ∅ | -| `never-published-document` | ✅ | ∅ | | `has-published-version` | ✅ | ✅ | -| `has-published-version-document` | ✅ | ✅ | | `modified` | ✅ | ✅ | | `unmodified` | ✅ | ✅ | | `published-without-draft` | ∅ | ✅ | | `published-with-draft` | ∅ | ✅ | +| `never-published-document` | ✅ | ∅ | +| `has-published-version-document` | ✅ | ✅ | ✅ returns the rows of the resolved status within the cohort; ∅ is always empty because the cohort has no row of that status. @@ -233,10 +183,231 @@ For example, `modified` with `status: 'draft'` returns the newer draft rows, whi Valid but empty combinations (the ∅ cells) do not return validation errors, they return no rows. ::: -## Combine with other parameters {#combine} +### More examples {#examples} + +The following examples show the most common cohorts. For the exact rows each combination returns, see [Which rows a combination returns](#status-combination). + +#### Modified documents {#modified} + +`modified` selects documents whose draft was edited since it was last published; `status` then chooses which row you get for those same documents: the newer draft or the currently-live published version. + + + + + +#### Documents never published in any locale {#document-scoped} + +`never-published-document` is document-scoped, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: + + + +#### Published entries without a draft {#published-without-draft-example} + +`published-without-draft` describes published rows, so it requires `status: 'published'`: + + + +#### Published entries with a draft {#published-with-draft-example} + +`published-with-draft` also describes published rows and requires `status: 'published'`: + + + +#### Use with `findOne()` and `findFirst()` {#find-one-find-first} + +If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: + + + +#### Count documents in a cohort {#count} + +Without `publicationFilter`, `count({ status: 'draft' })` counts every draft row, including drafts whose document already has a published version. Add `publicationFilter` to count only a specific cohort (see the [`status` documentation](/cms/api/document-service/status#count)): + +```js +const neverPublishedCount = await strapi + .documents('api::restaurant.restaurant') + .count({ + status: 'draft', + publicationFilter: 'never-published', + }); +``` + +### Combine with other parameters {#combine} `publicationFilter` is combined with other query parameters as a logical `AND`, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same cohort logic. -## Content Manager mapping {#content-manager} +### Content Manager mapping {#content-manager} In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the pair-scoped `never-published`). Other Status filter options use internal APIs rather than public `publicationFilter` values. From 290ff716f1f6438d05156e2b49968fcecc0794c2 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:38:24 +0200 Subject: [PATCH 14/75] Reduce cognitive load on REST publicationFilter page Front-loads the default status rule in the intro so readers meet it before the never-published example that depends on it, mirrors the Document Service Key terms box instead of dense prose, and splits the default status table into Returns and Why columns. --- .../docs/cms/api/rest/publication-filter.md | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index d4c5ff868b..f3bd6a9448 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -24,9 +24,20 @@ Add the optional `publicationFilter` query parameter to filter results by the re -The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. With Draft & Publish, each entry can have up to 2 rows for a locale: a *draft row* (`publishedAt` is empty) and a *published row* (`publishedAt` is set). +The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. `publicationFilter` filters results by the relationship between a document's draft and published versions, for example entries never published, or entries modified since they were last published. -A *cohort* is a set of documents grouped by how their draft and published rows relate, for example documents never published, or documents whose draft was edited since it was last published. You cannot get these groups by filtering on `publishedAt` yourself: questions like "was this ever published?" or "is the draft newer than the live version?" compare the 2 rows, not one. `publicationFilter` selects the cohort; [`status`](/cms/api/rest/status) then selects whether each result returns its draft or published row. +:::note Key terms +Strapi Draft & Publish stores each entry as up to 2 database rows for the same document and locale: + +- a *draft row* (`publishedAt` is empty) +- a *published row* (`publishedAt` is set) + +The [`status`](/cms/api/rest/status) parameter picks which of the 2 rows to read. + +`publicationFilter` instead selects a *cohort*: a group of documents defined by how their draft and published rows relate (for example, never published, or draft newer than published). Some cohorts compare the 2 rows, so they cannot be expressed by filtering on `publishedAt` alone. +::: + +`publicationFilter` selects the cohort first; `status` then decides which row (draft or published) is returned for each result. One rule explains most surprises on this page: when `status` is omitted, REST defaults to `status=published` **before** applying `publicationFilter`, so draft-only cohorts such as `never-published` return empty results unless you pass `status=draft`. [Understand the default `status`](#default-status) details each combination. This page shows how to query the most common cohorts over REST. For the full list of values, their exact definitions, and every `status` combination, see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter), which is the reference for the underlying model. @@ -192,13 +203,13 @@ await request(\`/api/restaurants?\${query}\`);` When `status` is omitted, REST defaults to `status=published` **before** applying `publicationFilter`. This is the most common source of unexpected empty results: a draft-only cohort such as `never-published` returns nothing under the default status. The table below shows the effect for the values used above: -| Query | Result | -| ----- | ------ | -| `?publicationFilter=never-published` | Empty (this cohort has draft rows only, and the default status is `published`) | -| `?status=draft&publicationFilter=never-published` | Never-published draft rows | -| `?publicationFilter=modified` | Published rows of modified documents | -| `?status=draft&publicationFilter=modified` | Draft rows of modified documents | -| `?publicationFilter=published-without-draft` | Published rows with no draft (default `status=published` is correct) | +| Query | Returns | Why | +| ----- | ------- | --- | +| `?publicationFilter=never-published` | Empty | The cohort has draft rows only; the default status is `published` | +| `?status=draft&publicationFilter=never-published` | Never-published draft rows | `status=draft` reads the draft row | +| `?publicationFilter=modified` | Published rows of modified documents | The default `status=published` reads the live row | +| `?status=draft&publicationFilter=modified` | Draft rows of modified documents | `status=draft` reads the newer draft | +| `?publicationFilter=published-without-draft` | Published rows with no draft | The default `status=published` is correct here | The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status) for the full comparison across API surfaces. From 4fbf7d04e5d8b490081bc0fd8d937cbd0c14b3ec Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:38:31 +0200 Subject: [PATCH 15/75] Rework GraphQL publicationFilter section for first-time readers Renames the heading to include the searchable argument name, opens with a plain-language sentence linking cohort to the canonical definition, isolates the PUBLISHED default pitfall in a caution admonition, and differentiates the two reference links. --- docusaurus/docs/cms/api/graphql.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docusaurus/docs/cms/api/graphql.md b/docusaurus/docs/cms/api/graphql.md index 502cce8fa3..16da6b1061 100644 --- a/docusaurus/docs/cms/api/graphql.md +++ b/docusaurus/docs/cms/api/graphql.md @@ -432,13 +432,15 @@ query Query($status: PublicationStatus) { } ``` -### Filter by derived publication cohort {#publication-filter} +### Filter with `publicationFilter` {#publication-filter} -If the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled, you can add a `publicationFilter` argument to built-in collection and single-type queries. The GraphQL plugin exposes the same cohorts as the REST API and Document Service API through the `PublicationFilter` enum. +If the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled, you can add a `publicationFilter` argument to built-in collection and single-type queries. It filters documents by the relationship between their draft and published versions (their [*cohort*](/cms/api/document-service/publication-filter#understand)): for example, drafts that were never published, or entries modified since they were last published. GraphQL exposes the same cohorts as the REST API and the Document Service API through the `PublicationFilter` enum. -Combine `publicationFilter` with `status` the same way as for REST (see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter#status-combination)). +`publicationFilter` selects the cohort first; the `status` argument then decides whether each result returns its draft or published row. -When `status` is omitted, GraphQL defaults to `PUBLISHED` before applying `publicationFilter` (same as REST). Example: `restaurants(publicationFilter: MODIFIED)` returns published rows in the modified cohort; use `status: DRAFT` to return draft rows instead. +:::caution +When `status` is omitted, GraphQL defaults to `PUBLISHED` before applying `publicationFilter` (same as REST). Draft-only values such as `NEVER_PUBLISHED` return no results unless you pass `status: DRAFT`. See [default `status` per API surface](/cms/api/document-service/publication-filter#default-status). +::: ```graphql title="Example: Fetch never-published draft documents" query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { @@ -473,6 +475,8 @@ Available enum values: | `PUBLISHED_WITHOUT_DRAFT` | `published-without-draft` | | `PUBLISHED_WITH_DRAFT` | `published-with-draft` | +To learn more, see the Document Service API reference: [available values and their scope](/cms/api/document-service/publication-filter#values), and [which rows each `status` × `publicationFilter` combination returns](/cms/api/document-service/publication-filter#status-combination). + ## Mutations Mutations in GraphQL are used to modify data (e.g. create, update, and delete data). From 5e3f4be38ac5a3e875ff856542ef3bbd310152bd Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:40:19 +0200 Subject: [PATCH 16/75] Replace cohort jargon with plain language across publicationFilter docs Users only interact with the parameter name and its values, and the term cohort only exists in Strapi internals, so plain phrasing like group of documents replaces it everywhere. A single parenthetical mention remains in The model section for readers who meet the internal term in the codebase. --- docusaurus/docs/cms/api/document-service.md | 8 ++--- .../document-service/publication-filter.md | 36 +++++++++---------- .../docs/cms/api/document-service/status.md | 2 +- docusaurus/docs/cms/api/graphql.md | 6 ++-- docusaurus/docs/cms/api/rest/parameters.md | 2 +- .../docs/cms/api/rest/publication-filter.md | 22 ++++++------ docusaurus/docs/cms/api/rest/status.md | 2 +- .../docs/cms/features/draft-and-publish.md | 8 ++--- 8 files changed, 43 insertions(+), 43 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service.md b/docusaurus/docs/cms/api/document-service.md index 4c6da2a301..5785a138c3 100644 --- a/docusaurus/docs/cms/api/document-service.md +++ b/docusaurus/docs/cms/api/document-service.md @@ -109,7 +109,7 @@ Syntax: `findOne(parameters: Params) => Document` { name: 'documentId', type: 'ID', required: true, description: 'Document id' }, { name: 'locale', type: 'String or undefined', required: false, description: 'Locale of the document to find. Defaults to the default locale. See locale docs.' }, { name: 'status', type: "'published' | 'draft'", required: false, description: 'If Draft & Publish is enabled: publication status. Can be published or draft. Default: draft. See status docs.' }, - { name: 'publicationFilter', type: 'String', required: false, description: 'If Draft & Publish is enabled: derived publication cohort to match before applying status. See publicationFilter docs.' }, + { name: 'publicationFilter', type: 'String', required: false, description: 'If Draft & Publish is enabled: select documents by how their draft and published versions relate, before applying status. See publicationFilter docs.' }, { name: 'fields', type: 'Object', required: false, description: 'Select fields to return. Defaults to all fields (except those not populated by default).' }, { name: 'populate', type: 'Object', required: false, description: 'Populate results with additional fields. Default: null.' }, ]} @@ -148,7 +148,7 @@ Syntax: `findFirst(parameters: Params) => Document` params={[ { name: 'locale', type: 'String or undefined', required: false, description: 'Locale of the documents to find. Defaults to the default locale. See locale docs.' }, { name: 'status', type: "'published' | 'draft'", required: false, description: 'If Draft & Publish is enabled: publication status. Can be published or draft. Default: draft. See status docs.' }, - { name: 'publicationFilter', type: 'String', required: false, description: 'If Draft & Publish is enabled: derived publication cohort to match before applying status. See publicationFilter docs.' }, + { name: 'publicationFilter', type: 'String', required: false, description: 'If Draft & Publish is enabled: select documents by how their draft and published versions relate, before applying status. See publicationFilter docs.' }, { name: 'filters', type: 'Object', required: false, description: 'Filters to use. Default: null.' }, { name: 'fields', type: 'Object', required: false, description: 'Select fields to return. Defaults to all fields (except those not populated by default).' }, { name: 'populate', type: 'Object', required: false, description: 'Populate results with additional fields. Default: null.' }, @@ -212,7 +212,7 @@ Syntax: `findMany(parameters: Params) => Document[]` params={[ { name: 'locale', type: 'String or undefined', required: false, description: 'Locale of the documents to find. Defaults to the default locale. See locale docs.' }, { name: 'status', type: "'published' | 'draft'", required: false, description: 'If Draft & Publish is enabled: publication status. Can be published or draft. Default: draft. See status docs.' }, - { name: 'publicationFilter', type: 'String', required: false, description: 'If Draft & Publish is enabled: derived publication cohort to match before applying status. See publicationFilter docs.' }, + { name: 'publicationFilter', type: 'String', required: false, description: 'If Draft & Publish is enabled: select documents by how their draft and published versions relate, before applying status. See publicationFilter docs.' }, { name: 'filters', type: 'Object', required: false, description: 'Filters to use. Default: null.' }, { name: 'fields', type: 'Object', required: false, description: 'Select fields to return. Defaults to all fields (except those not populated by default).' }, { name: 'populate', type: 'Object', required: false, description: 'Populate results with additional fields. Default: null.' }, @@ -609,7 +609,7 @@ Syntax: `count(parameters: Params) => number` params={[ { name: 'locale', type: 'String or null', required: false, description: 'Locale of the documents to count. Defaults to the default locale. See locale docs.' }, { name: 'status', type: "'published' | 'draft'", required: false, description: 'If Draft & Publish is enabled: publication status. published to count only published documents, draft to count draft documents (returns all documents). Default: draft. See status docs.' }, - { name: 'publicationFilter', type: 'String', required: false, description: 'If Draft & Publish is enabled: derived publication cohort to match before applying status. See publicationFilter docs.' }, + { name: 'publicationFilter', type: 'String', required: false, description: 'If Draft & Publish is enabled: select documents by how their draft and published versions relate, before applying status. See publicationFilter docs.' }, { name: 'filters', type: 'Object', required: false, description: 'Filters to use. Default: null.' }, ]} codeTabs={[ diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 29cedd8deb..2a588a826d 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -1,6 +1,6 @@ --- title: Using publicationFilter with the Document Service API -description: Use the publicationFilter parameter with Strapi's Document Service API to query derived Draft & Publish cohorts such as never-published or modified documents. +description: Use the publicationFilter parameter with Strapi's Document Service API to query documents by the relationship between their draft and published versions, such as never-published or modified documents. displayed_sidebar: cmsSidebar sidebar_label: Publication filter toc_max_heading_level: 4 @@ -37,7 +37,7 @@ Strapi Draft & Publish stores each entry as up to 2 database rows for the same d The `status` parameter picks which of the 2 rows to read. -`publicationFilter` instead selects a *cohort*: a group of documents defined by how their draft and published rows relate (for example, never published, or draft newer than published). Some cohorts compare the 2 rows, so they cannot be expressed by filtering on `publishedAt` alone. +`publicationFilter` instead selects a group of documents based on how their draft and published rows relate (for example, never published, or draft newer than published). Some of these questions compare the 2 rows, so they cannot be expressed by filtering on `publishedAt` alone. ::: :::prerequisites @@ -80,17 +80,17 @@ To read the drafts that have never been published, pass `status: 'draft'` (so yo ]} /> -The rest of this page is organized in 2 parts: [Understand `status` vs `publicationFilter`](#understand) explains the model behind cohorts, and the [API reference](#reference) lists all values, their exact definitions, and more examples. You can use the reference directly and come back to the explanations when a combination surprises you. +The rest of this page is organized in 2 parts: [Understand `status` vs `publicationFilter`](#understand) explains the model behind the filter, and the [API reference](#reference) lists all values, their exact definitions, and more examples. You can use the reference directly and come back to the explanations when a combination surprises you. ## Understand `status` vs `publicationFilter` {#understand} -This section explains the model behind `publicationFilter`. You do not need it to use the common cohorts shown in the [examples](#examples), but it is what lets you predict the result of any `status` × `publicationFilter` combination. +This section explains the model behind `publicationFilter`. You do not need it to use the common values shown in the [examples](#examples), but it is what lets you predict the result of any `status` × `publicationFilter` combination. ### The model {#the-model} -A document can have up to 2 rows for the same `(documentId, locale)` pair: a *draft row* (`publishedAt: null`) and a *published row* (non-null `publishedAt`). The [`status`](/cms/api/document-service/status) parameter picks which of these 2 rows to read, while `publicationFilter` selects the *cohort* of documents first, based on how their draft and published rows relate. +A document can have up to 2 rows for the same `(documentId, locale)` pair: a *draft row* (`publishedAt: null`) and a *published row* (non-null `publishedAt`). The [`status`](/cms/api/document-service/status) parameter picks which of these 2 rows to read, while `publicationFilter` first selects the group of documents to consider, based on how their draft and published rows relate. -Strapi resolves the cohort first, then returns the row that matches the resolved `status`. +Strapi selects the matching documents first, then returns the row that matches the resolved `status`. (Strapi internals refer to these groups of documents as *publication cohorts*, but you never need that term to use the API.) ### Scope: pair vs. document {#scope} @@ -136,7 +136,7 @@ Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails `published-without-draft` and `published-with-draft` describe published rows, so they only return results with `status: 'published'`. -### Cohort definitions {#cohort-definitions} +### Exact definitions {#cohort-definitions} The following table lists the same 8 values in the same order as [above](#values), this time with the exact predicate each one applies: @@ -157,7 +157,7 @@ The following table lists the same 8 values in the same order as [above](#values ### Which rows a combination returns {#status-combination} -Because the cohort is resolved before `status` selects a row, some combinations always return nothing. The grid below shows which `status` × `publicationFilter` pairs return rows and which are always empty: +Because the matching documents are selected before `status` picks a row, some combinations always return nothing. The grid below shows which `status` × `publicationFilter` pairs return rows and which are always empty: | `publicationFilter` | With `status: 'draft'` | With `status: 'published'` | | ------------------- | :--------------------: | :------------------------: | @@ -170,12 +170,12 @@ Because the cohort is resolved before `status` selects a row, some combinations | `never-published-document` | ✅ | ∅ | | `has-published-version-document` | ✅ | ✅ | -✅ returns the rows of the resolved status within the cohort; ∅ is always empty because the cohort has no row of that status. +✅ returns the rows of the resolved status within the selected group of documents; ∅ is always empty because that group has no row of that status. When a combination returns rows, it returns them as follows: -- With `status: 'draft'`, you get the draft rows of documents in the cohort. -- With `status: 'published'`, you get the published rows of documents in the cohort. +- With `status: 'draft'`, you get the draft rows of the selected documents. +- With `status: 'published'`, you get the published rows of the selected documents. For example, `modified` with `status: 'draft'` returns the newer draft rows, while `modified` with `status: 'published'` returns the currently-live published rows of those same documents. @@ -185,7 +185,7 @@ Valid but empty combinations (the ∅ cells) do not return validation errors, th ### More examples {#examples} -The following examples show the most common cohorts. For the exact rows each combination returns, see [Which rows a combination returns](#status-combination). +The following examples show the most common values. For the exact rows each combination returns, see [Which rows a combination returns](#status-combination). #### Modified documents {#modified} @@ -365,13 +365,13 @@ The following examples show the most common cohorts. For the exact rows each com #### Use with `findOne()` and `findFirst()` {#find-one-find-first} -If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: +If the requested document (and locale, when applicable) does not match the filter, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: -#### Count documents in a cohort {#count} +#### Count only matching documents {#count} -Without `publicationFilter`, `count({ status: 'draft' })` counts every draft row, including drafts whose document already has a published version. Add `publicationFilter` to count only a specific cohort (see the [`status` documentation](/cms/api/document-service/status#count)): +Without `publicationFilter`, `count({ status: 'draft' })` counts every draft row, including drafts whose document already has a published version. Add `publicationFilter` to count only the documents that match a given value (see the [`status` documentation](/cms/api/document-service/status#count)): ```js const neverPublishedCount = await strapi @@ -406,7 +406,7 @@ const neverPublishedCount = await strapi ### Combine with other parameters {#combine} -`publicationFilter` is combined with other query parameters as a logical `AND`, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same cohort logic. +`publicationFilter` is combined with other query parameters as a logical `AND`, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same filter logic. ### Content Manager mapping {#content-manager} diff --git a/docusaurus/docs/cms/api/document-service/status.md b/docusaurus/docs/cms/api/document-service/status.md index d954ff6f7e..e4b3aae5cd 100644 --- a/docusaurus/docs/cms/api/document-service/status.md +++ b/docusaurus/docs/cms/api/document-service/status.md @@ -36,7 +36,7 @@ By default the [Document Service API](/cms/api/document-service) returns the dra Passing `{ status: 'draft' }` to a Document Service API query returns the same results as not passing any `status` parameter. ::: -For derived publication cohorts (never-published, modified, and others), see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). +To select documents by how their draft and published versions relate (never-published, modified, and others), see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). ## Get the published version with `findOne()` {#find-one} diff --git a/docusaurus/docs/cms/api/graphql.md b/docusaurus/docs/cms/api/graphql.md index 16da6b1061..ff32e78c24 100644 --- a/docusaurus/docs/cms/api/graphql.md +++ b/docusaurus/docs/cms/api/graphql.md @@ -434,9 +434,9 @@ query Query($status: PublicationStatus) { ### Filter with `publicationFilter` {#publication-filter} -If the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled, you can add a `publicationFilter` argument to built-in collection and single-type queries. It filters documents by the relationship between their draft and published versions (their [*cohort*](/cms/api/document-service/publication-filter#understand)): for example, drafts that were never published, or entries modified since they were last published. GraphQL exposes the same cohorts as the REST API and the Document Service API through the `PublicationFilter` enum. +If the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled, you can add a `publicationFilter` argument to built-in collection and single-type queries. It filters documents by the [relationship between their draft and published versions](/cms/api/document-service/publication-filter#understand): for example, drafts that were never published, or entries modified since they were last published. GraphQL exposes the same values as the REST API and the Document Service API through the `PublicationFilter` enum. -`publicationFilter` selects the cohort first; the `status` argument then decides whether each result returns its draft or published row. +`publicationFilter` selects the group of documents first; the `status` argument then decides whether each result returns its draft or published row. :::caution When `status` is omitted, GraphQL defaults to `PUBLISHED` before applying `publicationFilter` (same as REST). Draft-only values such as `NEVER_PUBLISHED` return no results unless you pass `status: DRAFT`. See [default `status` per API surface](/cms/api/document-service/publication-filter#default-status). @@ -452,7 +452,7 @@ query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { } ``` -```graphql title="Example: Modified cohort with default PUBLISHED status" +```graphql title="Example: Modified documents with default PUBLISHED status" query Query { restaurants(publicationFilter: MODIFIED) { documentId diff --git a/docusaurus/docs/cms/api/rest/parameters.md b/docusaurus/docs/cms/api/rest/parameters.md index fae926445c..041364afb0 100644 --- a/docusaurus/docs/cms/api/rest/parameters.md +++ b/docusaurus/docs/cms/api/rest/parameters.md @@ -32,7 +32,7 @@ The following API parameters are available: | `filters` | Object | [Filter the response](/cms/api/rest/filters) | | `locale` | String | [Select a locale](/cms/api/rest/locale) | | `status` | String | [Select the Draft & Publish status](/cms/api/rest/status) | -| `publicationFilter` | String | [Select a derived Draft & Publish cohort](/cms/api/rest/publication-filter) | +| `publicationFilter` | String | [Select documents by how their draft and published versions relate](/cms/api/rest/publication-filter) | | `populate` | String or Object | [Populate relations, components, or dynamic zones](/cms/api/rest/populate-select#population) | | `fields` | Array | [Select only specific fields to display](/cms/api/rest/populate-select#field-selection) | | `sort` | String or Array | [Sort the response](/cms/api/rest/sort-pagination.md#sorting) | diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index f3bd6a9448..e3a51ddfaa 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -1,6 +1,6 @@ --- title: Publication filter -description: Use the publicationFilter parameter with Strapi's REST API to query derived Draft & Publish cohorts such as never-published or modified documents. +description: Use the publicationFilter parameter with Strapi's REST API to query documents by the relationship between their draft and published versions, such as never-published or modified documents. sidebarDepth: 3 sidebar_label: Publication filter next: ./populate-select.md @@ -20,7 +20,7 @@ tags: -Add the optional `publicationFilter` query parameter to filter results by the relationship between a document's draft and published versions, for example never-published or modified entries. The [`status`](/cms/api/rest/status) parameter still selects whether each result returns its draft or published row. REST defaults to `status=published`, so pass `status=draft` for draft-only cohorts. +Add the optional `publicationFilter` query parameter to filter results by the relationship between a document's draft and published versions, for example never-published or modified entries. The [`status`](/cms/api/rest/status) parameter still selects whether each result returns its draft or published row. REST defaults to `status=published`, so pass `status=draft` for values that only match drafts. @@ -34,12 +34,12 @@ Strapi Draft & Publish stores each entry as up to 2 database rows for the same d The [`status`](/cms/api/rest/status) parameter picks which of the 2 rows to read. -`publicationFilter` instead selects a *cohort*: a group of documents defined by how their draft and published rows relate (for example, never published, or draft newer than published). Some cohorts compare the 2 rows, so they cannot be expressed by filtering on `publishedAt` alone. +`publicationFilter` instead selects a group of documents based on how their draft and published rows relate (for example, never published, or draft newer than published). Some of these questions compare the 2 rows, so they cannot be expressed by filtering on `publishedAt` alone. ::: -`publicationFilter` selects the cohort first; `status` then decides which row (draft or published) is returned for each result. One rule explains most surprises on this page: when `status` is omitted, REST defaults to `status=published` **before** applying `publicationFilter`, so draft-only cohorts such as `never-published` return empty results unless you pass `status=draft`. [Understand the default `status`](#default-status) details each combination. +`publicationFilter` selects the group of documents first; `status` then decides which row (draft or published) is returned for each result. One rule explains most surprises on this page: when `status` is omitted, REST defaults to `status=published` **before** applying `publicationFilter`, so values that only match drafts, such as `never-published`, return empty results unless you pass `status=draft`. [Understand the default `status`](#default-status) details each combination. -This page shows how to query the most common cohorts over REST. For the full list of values, their exact definitions, and every `status` combination, see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter), which is the reference for the underlying model. +This page shows how to use the most common `publicationFilter` values over REST. For the full list of values, their exact definitions, and every `status` combination, see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter), which is the reference for the underlying model. :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. @@ -99,12 +99,12 @@ await request(\`/api/restaurants?\${query}\`);` ## Get modified documents {#modified} -The `modified` cohort contains documents whose draft is newer than their published version. With no `status` parameter, REST returns the **published** rows of those documents. Pass `status=draft` to return the newer draft rows instead: +`modified` matches documents whose draft is newer than their published version. With no `status` parameter, REST returns the **published** rows of those documents. Pass `status=draft` to return the newer draft rows instead:
diff --git a/docusaurus/docs/cms/features/draft-and-publish.md b/docusaurus/docs/cms/features/draft-and-publish.md index 4e570e5d23..a08e29a1fe 100644 --- a/docusaurus/docs/cms/features/draft-and-publish.md +++ b/docusaurus/docs/cms/features/draft-and-publish.md @@ -185,19 +185,19 @@ To unpublish several entries at the same time: ### Usage with APIs -Draft or published content can be requested, created, updated, and deleted using the `status` parameter through the various front-end APIs accessible from [Strapi's Content API](/cms/api/content-api). To query derived cohorts such as never-published or modified documents, use the `publicationFilter` parameter (REST and GraphQL) or the equivalent Document Service API option. +Draft or published content can be requested, created, updated, and deleted using the `status` parameter through the various front-end APIs accessible from [Strapi's Content API](/cms/api/content-api). To query documents by the relationship between their draft and published versions, such as never-published or modified documents, use the `publicationFilter` parameter (REST and GraphQL) or the equivalent Document Service API option. - + - + On the back-end server of Strapi, the Document Service API can also query and manage draft and published content: - + From b43e70c1f8019e18a57e3481e38308d7c271079a Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:53:01 +0200 Subject: [PATCH 17/75] Use bullet points on quick example description for readability --- .../docs/cms/api/document-service/publication-filter.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 2a588a826d..ba8753b574 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -46,7 +46,10 @@ The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled o ## Quick example {#quick-example} -To read the drafts that have never been published, pass `status: 'draft'` (so you read the draft row) and `publicationFilter: 'never-published'` (so you only keep documents with no published version): +To read the drafts that have never been published, pass: + +- `status: 'draft'` (so you read the draft row) +- and `publicationFilter: 'never-published'` (so you only keep documents with no published version): Date: Thu, 2 Jul 2026 16:53:18 +0200 Subject: [PATCH 18/75] Format quick example (indentation) properly --- .../document-service/publication-filter.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index ba8753b574..3326fb13a0 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -60,8 +60,8 @@ To read the drafts that have never been published, pass: { label: 'JavaScript', code: `await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'never-published', + status: 'draft', + publicationFilter: 'never-published', });` } ]} @@ -69,15 +69,15 @@ To read the drafts that have never been published, pass: { status: 200, statusText: 'OK', - body: `[ - { - documentId: "a1b2c3d4e5f6g7h8i9j0klm", - name: "New Restaurant", - publishedAt: null, - locale: "en", // default locale + body: `[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "New Restaurant", + publishedAt: null, + locale: "en", // default locale + // … + } // … - } - // … ]` } ]} From 6bcfc571bd564e0ad354a3754e7b9f1cdf706e81 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:53:36 +0200 Subject: [PATCH 19/75] Add line break after quick example --- docusaurus/docs/cms/api/document-service/publication-filter.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 3326fb13a0..4f011f4798 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -83,6 +83,7 @@ To read the drafts that have never been published, pass: ]} /> +
The rest of this page is organized in 2 parts: [Understand `status` vs `publicationFilter`](#understand) explains the model behind the filter, and the [API reference](#reference) lists all values, their exact definitions, and more examples. You can use the reference directly and come back to the explanations when a combination surprises you. ## Understand `status` vs `publicationFilter` {#understand} From 55e886c09ad65dc904a870e500e86d2440a0fca5 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:53:47 +0200 Subject: [PATCH 20/75] Implement parallel heading structure for H2s --- docusaurus/docs/cms/api/document-service/publication-filter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 4f011f4798..1669d15b6e 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -86,7 +86,7 @@ To read the drafts that have never been published, pass:
The rest of this page is organized in 2 parts: [Understand `status` vs `publicationFilter`](#understand) explains the model behind the filter, and the [API reference](#reference) lists all values, their exact definitions, and more examples. You can use the reference directly and come back to the explanations when a combination surprises you. -## Understand `status` vs `publicationFilter` {#understand} +## `status` vs `publicationFilter` {#understand} This section explains the model behind `publicationFilter`. You do not need it to use the common values shown in the [examples](#examples), but it is what lets you predict the result of any `status` × `publicationFilter` combination. From 6150caad449daf2c8be353377c5352db32ac1d61 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:59:44 +0200 Subject: [PATCH 21/75] Deduplicate intro and model explanations on Document Service publicationFilter page The Key terms box repeated what The model already states, and the two intro paragraphs said the same thing twice: the core concept was stated 5 times before the reference part. The intro is now a single paragraph, The model is the single formal statement with a numbered 2-step resolution, and the Default status opening no longer appears to contradict the step order. --- .../document-service/publication-filter.md | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 1669d15b6e..44f4bd77e7 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -25,20 +25,7 @@ Use the optional `publicationFilter` parameter to query documents by the relatio -The [`status`](/cms/api/document-service/status) parameter answers "do I want the draft or the published version?". The `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". - -For example, you can ask for drafts that were never published, or for entries whose draft has unsaved changes compared to what is live. `publicationFilter` selects that group of documents first; `status` then decides which row (draft or published) is returned for each. - -:::note Key terms -Strapi Draft & Publish stores each entry as up to 2 database rows for the same document and locale: - -- a *draft row* (`publishedAt` is empty) -- a *published row* (`publishedAt` is set) - -The `status` parameter picks which of the 2 rows to read. - -`publicationFilter` instead selects a group of documents based on how their draft and published rows relate (for example, never published, or draft newer than published). Some of these questions compare the 2 rows, so they cannot be expressed by filtering on `publishedAt` alone. -::: +The [`status`](/cms/api/document-service/status) parameter answers "do I want the draft or the published version?". The `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". For example: drafts that were never published, or entries whose draft has unsaved changes compared to what is live. :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. @@ -84,7 +71,7 @@ To read the drafts that have never been published, pass: />
-The rest of this page is organized in 2 parts: [Understand `status` vs `publicationFilter`](#understand) explains the model behind the filter, and the [API reference](#reference) lists all values, their exact definitions, and more examples. You can use the reference directly and come back to the explanations when a combination surprises you. +The rest of this page is organized in 2 parts: [`status` vs `publicationFilter`](#understand) explains the model behind the filter, and the [API reference](#reference) lists all values, their exact definitions, and more examples. You can use the reference directly and come back to the explanations when a combination surprises you. ## `status` vs `publicationFilter` {#understand} @@ -92,9 +79,17 @@ This section explains the model behind `publicationFilter`. You do not need it t ### The model {#the-model} -A document can have up to 2 rows for the same `(documentId, locale)` pair: a *draft row* (`publishedAt: null`) and a *published row* (non-null `publishedAt`). The [`status`](/cms/api/document-service/status) parameter picks which of these 2 rows to read, while `publicationFilter` first selects the group of documents to consider, based on how their draft and published rows relate. +Strapi Draft & Publish stores each entry as up to 2 database rows for the same `(documentId, locale)` pair: + +- a *draft row* (`publishedAt` is null) +- a *published row* (`publishedAt` is set) + +A query resolves in 2 steps: + +1. `publicationFilter` selects the documents to consider, based on how their draft and published rows relate. +2. [`status`](/cms/api/document-service/status) picks which of the 2 rows is returned for each selected document. -Strapi selects the matching documents first, then returns the row that matches the resolved `status`. (Strapi internals refer to these groups of documents as *publication cohorts*, but you never need that term to use the API.) +Some questions ("was this ever published?", "is the draft newer than the live version?") compare the 2 rows, which is why they cannot be expressed by filtering on `publishedAt` alone. (Strapi internals refer to these groups of documents as *publication cohorts*, but you never need that term to use the API.) ### Scope: pair vs. document {#scope} @@ -107,7 +102,7 @@ Without i18n, both scopes behave the same. ### Default `status` per API surface {#default-status} -`publicationFilter` is applied *after* `status` is resolved, so the default `status` of your API surface affects what you get back: +When you omit `status`, each API surface fills in its own default before the query runs, and that default decides which rows you get back: | API surface | Default `status` when omitted | | ----------- | ----------------------------- | From d0daae1418d34cb13753a220d8d4a65ae1adad21 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:10:23 +0200 Subject: [PATCH 22/75] Flatten Document Service publicationFilter page around a use-case table Removes the theory section entirely: the intro keeps only the two-row model in one paragraph, a use-case table maps each goal to its status and publicationFilter values with links to full examples, and every example is a flat H2 visible in the secondary sidebar. The scope concept, the combination grid, and the per-surface defaults are compressed into the table wording, one note, and one intro sentence. REST and GraphQL links to the removed anchors are updated. --- .../document-service/publication-filter.md | 160 ++++-------------- docusaurus/docs/cms/api/graphql.md | 6 +- .../docs/cms/api/rest/publication-filter.md | 2 +- 3 files changed, 40 insertions(+), 128 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 44f4bd77e7..c23ed414e5 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -3,7 +3,6 @@ title: Using publicationFilter with the Document Service API description: Use the publicationFilter parameter with Strapi's Document Service API to query documents by the relationship between their draft and published versions, such as never-published or modified documents. displayed_sidebar: cmsSidebar sidebar_label: Publication filter -toc_max_heading_level: 4 tags: - API - Content API @@ -27,6 +26,8 @@ Use the optional `publicationFilter` parameter to query documents by the relatio The [`status`](/cms/api/document-service/status) parameter answers "do I want the draft or the published version?". The `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". For example: drafts that were never published, or entries whose draft has unsaved changes compared to what is live. +With Draft & Publish, Strapi stores each entry as up to 2 database rows per locale: a *draft row* and a *published row*. `publicationFilter` selects which documents to consider, based on how these 2 rows relate; `status` picks which of the 2 rows is returned for each of them. The Document Service API returns draft rows when `status` is omitted; REST and GraphQL return published rows instead, so the equivalent queries there need an explicit `status` (see [REST API: `publicationFilter`](/cms/api/rest/publication-filter)). + :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. ::: @@ -71,124 +72,35 @@ To read the drafts that have never been published, pass: />
-The rest of this page is organized in 2 parts: [`status` vs `publicationFilter`](#understand) explains the model behind the filter, and the [API reference](#reference) lists all values, their exact definitions, and more examples. You can use the reference directly and come back to the explanations when a combination surprises you. - -## `status` vs `publicationFilter` {#understand} - -This section explains the model behind `publicationFilter`. You do not need it to use the common values shown in the [examples](#examples), but it is what lets you predict the result of any `status` × `publicationFilter` combination. - -### The model {#the-model} - -Strapi Draft & Publish stores each entry as up to 2 database rows for the same `(documentId, locale)` pair: - -- a *draft row* (`publishedAt` is null) -- a *published row* (`publishedAt` is set) - -A query resolves in 2 steps: - -1. `publicationFilter` selects the documents to consider, based on how their draft and published rows relate. -2. [`status`](/cms/api/document-service/status) picks which of the 2 rows is returned for each selected document. - -Some questions ("was this ever published?", "is the draft newer than the live version?") compare the 2 rows, which is why they cannot be expressed by filtering on `publishedAt` alone. (Strapi internals refer to these groups of documents as *publication cohorts*, but you never need that term to use the API.) - -### Scope: pair vs. document {#scope} - -Each `publicationFilter` value has a *scope*, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: - -- a *pair-scoped* value looks at 1 locale at a time (a `documentId` + `locale` pair). For example, `never-published` matches documents never published in that locale. -- a *document-scoped* value (its name ends in `-document`) looks at the whole document across all its locales. For example, `never-published-document` matches documents never published in any locale. - -Without i18n, both scopes behave the same. - -### Default `status` per API surface {#default-status} - -When you omit `status`, each API surface fills in its own default before the query runs, and that default decides which rows you get back: - -| API surface | Default `status` when omitted | -| ----------- | ----------------------------- | -| Document Service API (direct) | `draft` | -| [REST API](/cms/api/rest/publication-filter) | `published` | -| [GraphQL API](/cms/api/graphql#publication-filter) | `PUBLISHED` | - -This matters for pair-scoped values that only contain draft rows, such as `never-published`: with the REST or GraphQL defaults (`published`), the query returns an empty result set unless you explicitly pass `status=draft` / `status: DRAFT`. The Document Service defaults to `draft`, so the [quick example](#quick-example) above works without setting `status`. - -## API reference {#reference} - -This section lists the accepted values, their exact definitions, what each `status` × `publicationFilter` combination returns, and more examples. - -### Available values {#values} - -`publicationFilter` accepts one of the following kebab-case values. REST and the Document Service API use these strings directly; GraphQL exposes the same set through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). The `Scope (i18n)` column refers to the [pair vs. document scope](#scope). - -| Value | What it selects | Scope (i18n) | -| ----- | --------------- | ----- | -| `never-published` | Documents never published in that locale | Pair | -| `has-published-version` | Documents that have both a draft and a published version | Pair | -| `modified` | Documents whose draft was edited since it was last published | Pair | -| `unmodified` | Documents whose draft has not changed since it was last published | Pair | -| `published-without-draft` | Published entries with no matching draft | Pair | -| `published-with-draft` | Published entries that also have a matching draft | Pair | -| `never-published-document` | Documents never published in any locale | Document | -| `has-published-version-document` | Documents published in at least one locale | Document | - -Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). - -`published-without-draft` and `published-with-draft` describe published rows, so they only return results with `status: 'published'`. - -### Exact definitions {#cohort-definitions} - -The following table lists the same 8 values in the same order as [above](#values), this time with the exact predicate each one applies: - -| Value | Scope (i18n) | A `(documentId, locale)` pair matches when… | -| ----- | ----- | -------------------------------------------- | -| `never-published` | Pair | no row with a non-null `publishedAt` exists for the pair | -| `has-published-version` | Pair | both a draft row and a published row exist for the pair | -| `modified` | Pair | both rows exist and `draft.updatedAt > published.updatedAt` | -| `unmodified` | Pair | both rows exist and `draft.updatedAt <= published.updatedAt` | -| `published-without-draft` | Pair | a published row exists for the pair and no draft row exists | -| `published-with-draft` | Pair | a published row exists for the pair and a draft row also exists | -| `never-published-document` | Document | no row with a non-null `publishedAt` exists for the `documentId` in any locale | -| `has-published-version-document` | Document | at least one published row exists for the `documentId` in any locale | - -:::note -`has-published-version` excludes orphan published rows (a published row with no draft sibling for the same pair). Those rows match `published-without-draft` instead when `status` is `published`. +The table below lists all supported use cases, and the rest of the page shows complete examples. + +## Use cases {#values} + +`publicationFilter` accepts the kebab-case values listed below; GraphQL exposes the same set through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). Strapi internals refer to the groups of documents these values select as *publication cohorts*, but you never need that term to use the API. + +| I want to… | Parameters | Full example | +| ---------- | ---------- | ------------ | +| Find drafts never published in a given locale | `status: 'draft'`, `publicationFilter: 'never-published'` | [Quick example](#quick-example) | +| Find the newer drafts of entries modified since their last publication | `status: 'draft'`, `publicationFilter: 'modified'` | [Modified documents](#modified) | +| Find the currently-live version of those same modified entries | `status: 'published'`, `publicationFilter: 'modified'` | [Modified documents](#modified) | +| Find drafts that have not changed since their last publication | `status: 'draft'` or `'published'`, `publicationFilter: 'unmodified'` | – | +| Find entries that have both a draft and a published version | `status: 'draft'` or `'published'`, `publicationFilter: 'has-published-version'` | – | +| Find published entries that have no draft counterpart | `status: 'published'`, `publicationFilter: 'published-without-draft'` | [Published entries without a draft](#published-without-draft-example) | +| Find published entries that also have a draft | `status: 'published'`, `publicationFilter: 'published-with-draft'` | [Published entries with a draft](#published-with-draft-example) | +| Find drafts of documents never published in any locale | `status: 'draft'`, `publicationFilter: 'never-published-document'` | [Documents never published in any locale](#document-scoped) | +| Find documents published in at least one locale | `status: 'draft'` or `'published'`, `publicationFilter: 'has-published-version-document'` | – | +| Check whether one specific document matches a value | `publicationFilter` with `findOne()` or `findFirst()` | [Use with findOne() and findFirst()](#find-one-find-first) | +| Count only the documents that match a value | `publicationFilter` with `count()` | [Count only matching documents](#count) | + +:::note Values ending in -document and localization +Values ending in `-document` consider all locales of a document, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: for example, `never-published-document` excludes a document as soon as one of its locales is published. All other values consider one locale at a time. Without i18n, both variants behave the same. ::: -### Which rows a combination returns {#status-combination} - -Because the matching documents are selected before `status` picks a row, some combinations always return nothing. The grid below shows which `status` × `publicationFilter` pairs return rows and which are always empty: - -| `publicationFilter` | With `status: 'draft'` | With `status: 'published'` | -| ------------------- | :--------------------: | :------------------------: | -| `never-published` | ✅ | ∅ | -| `has-published-version` | ✅ | ✅ | -| `modified` | ✅ | ✅ | -| `unmodified` | ✅ | ✅ | -| `published-without-draft` | ∅ | ✅ | -| `published-with-draft` | ∅ | ✅ | -| `never-published-document` | ✅ | ∅ | -| `has-published-version-document` | ✅ | ✅ | - -✅ returns the rows of the resolved status within the selected group of documents; ∅ is always empty because that group has no row of that status. - -When a combination returns rows, it returns them as follows: - -- With `status: 'draft'`, you get the draft rows of the selected documents. -- With `status: 'published'`, you get the published rows of the selected documents. - -For example, `modified` with `status: 'draft'` returns the newer draft rows, while `modified` with `status: 'published'` returns the currently-live published rows of those same documents. - -:::note -Valid but empty combinations (the ∅ cells) do not return validation errors, they return no rows. -::: - -### More examples {#examples} - -The following examples show the most common values. For the exact rows each combination returns, see [Which rows a combination returns](#status-combination). +Pairing a value with the opposite `status` from the table above is valid but returns no rows rather than an error: for example, `never-published` with `status: 'published'` returns an empty result, because these documents have no published row yet. -#### Modified documents {#modified} +## Modified documents {#modified} -`modified` selects documents whose draft was edited since it was last published; `status` then chooses which row you get for those same documents: the newer draft or the currently-live published version. +`modified` selects documents whose draft was edited since it was last published (the draft row's `updatedAt` is more recent than the published row's); `status` then chooses which row you get for those same documents: the newer draft or the currently-live published version. -#### Documents never published in any locale {#document-scoped} +## Documents never published in any locale {#document-scoped} -`never-published-document` is document-scoped, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: +`never-published-document` considers all locales, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: -#### Published entries without a draft {#published-without-draft-example} +## Published entries without a draft {#published-without-draft-example} `published-without-draft` describes published rows, so it requires `status: 'published'`: @@ -326,7 +238,7 @@ The following examples show the most common values. For the exact rows each comb ]} /> -#### Published entries with a draft {#published-with-draft-example} +## Published entries with a draft {#published-with-draft-example} `published-with-draft` also describes published rows and requires `status: 'published'`: @@ -362,7 +274,7 @@ The following examples show the most common values. For the exact rows each comb ]} /> -#### Use with `findOne()` and `findFirst()` {#find-one-find-first} +## Use with `findOne()` and `findFirst()` {#find-one-find-first} If the requested document (and locale, when applicable) does not match the filter, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: @@ -390,7 +302,7 @@ If the requested document (and locale, when applicable) does not match the filte ]} /> -#### Count only matching documents {#count} +## Count only matching documents {#count} Without `publicationFilter`, `count({ status: 'draft' })` counts every draft row, including drafts whose document already has a published version. Add `publicationFilter` to count only the documents that match a given value (see the [`status` documentation](/cms/api/document-service/status#count)): @@ -403,10 +315,10 @@ const neverPublishedCount = await strapi }); ``` -### Combine with other parameters {#combine} +## Combine with other parameters {#combine} `publicationFilter` is combined with other query parameters as a logical `AND`, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same filter logic. -### Content Manager mapping {#content-manager} +## Content Manager mapping {#content-manager} -In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the pair-scoped `never-published`). Other Status filter options use internal APIs rather than public `publicationFilter` values. +In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the per-locale `never-published`). Other Status filter options use internal APIs rather than public `publicationFilter` values. diff --git a/docusaurus/docs/cms/api/graphql.md b/docusaurus/docs/cms/api/graphql.md index ff32e78c24..7ff188e6a8 100644 --- a/docusaurus/docs/cms/api/graphql.md +++ b/docusaurus/docs/cms/api/graphql.md @@ -434,12 +434,12 @@ query Query($status: PublicationStatus) { ### Filter with `publicationFilter` {#publication-filter} -If the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled, you can add a `publicationFilter` argument to built-in collection and single-type queries. It filters documents by the [relationship between their draft and published versions](/cms/api/document-service/publication-filter#understand): for example, drafts that were never published, or entries modified since they were last published. GraphQL exposes the same values as the REST API and the Document Service API through the `PublicationFilter` enum. +If the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled, you can add a `publicationFilter` argument to built-in collection and single-type queries. It filters documents by the [relationship between their draft and published versions](/cms/api/document-service/publication-filter): for example, drafts that were never published, or entries modified since they were last published. GraphQL exposes the same values as the REST API and the Document Service API through the `PublicationFilter` enum. `publicationFilter` selects the group of documents first; the `status` argument then decides whether each result returns its draft or published row. :::caution -When `status` is omitted, GraphQL defaults to `PUBLISHED` before applying `publicationFilter` (same as REST). Draft-only values such as `NEVER_PUBLISHED` return no results unless you pass `status: DRAFT`. See [default `status` per API surface](/cms/api/document-service/publication-filter#default-status). +When `status` is omitted, GraphQL defaults to `PUBLISHED` before applying `publicationFilter` (same as REST). Draft-only values such as `NEVER_PUBLISHED` return no results unless you pass `status: DRAFT`. ::: ```graphql title="Example: Fetch never-published draft documents" @@ -475,7 +475,7 @@ Available enum values: | `PUBLISHED_WITHOUT_DRAFT` | `published-without-draft` | | `PUBLISHED_WITH_DRAFT` | `published-with-draft` | -To learn more, see the Document Service API reference: [available values and their scope](/cms/api/document-service/publication-filter#values), and [which rows each `status` × `publicationFilter` combination returns](/cms/api/document-service/publication-filter#status-combination). +To learn more, see the [use cases and accepted values](/cms/api/document-service/publication-filter#values) on the Document Service API page. ## Mutations diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index e3a51ddfaa..7ce4dff5fe 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -211,7 +211,7 @@ When `status` is omitted, REST defaults to `status=published` **before** applyin | `?status=draft&publicationFilter=modified` | Draft rows of modified documents | `status=draft` reads the newer draft | | `?publicationFilter=published-without-draft` | Published rows with no draft | The default `status=published` is correct here | -The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status) for the full comparison across API surfaces. +The Document Service API defaults to `status=draft` instead (see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter)). ## Reference: accepted values {#values} From 3867edbf0ace8efdc999e7708231f37e70a6f231 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:26:49 +0200 Subject: [PATCH 23/75] Add Available values table before use cases on publicationFilter page Lists the 8 accepted values with a plain-language description in a scannable two-column table, so readers can see the full set at a glance instead of extracting it from the use-case rows. The i18n note and the internal-term mention move to this section. --- .../document-service/publication-filter.md | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index c23ed414e5..06feb0ec08 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -72,11 +72,30 @@ To read the drafts that have never been published, pass: />
-The table below lists all supported use cases, and the rest of the page shows complete examples. +The next section lists all accepted values, [Use cases](#use-cases) maps common goals to the parameters to pass, and the rest of the page shows complete examples. -## Use cases {#values} +## Available values {#values} -`publicationFilter` accepts the kebab-case values listed below; GraphQL exposes the same set through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). Strapi internals refer to the groups of documents these values select as *publication cohorts*, but you never need that term to use the API. +`publicationFilter` accepts one of the following kebab-case values. GraphQL exposes the same set through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). + +| Value | Selects | +| ----- | ------- | +| `never-published` | Documents never published in a given locale | +| `has-published-version` | Documents that have both a draft and a published version | +| `modified` | Documents whose draft was edited since it was last published | +| `unmodified` | Documents whose draft has not changed since it was last published | +| `published-without-draft` | Published entries with no draft counterpart | +| `published-with-draft` | Published entries that also have a draft | +| `never-published-document` | Documents never published in any locale | +| `has-published-version-document` | Documents published in at least one locale | + +:::note Values ending in -document and localization +Values ending in `-document` consider all locales of a document, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: for example, `never-published-document` excludes a document as soon as one of its locales is published. All other values consider one locale at a time. Without i18n, both variants behave the same. +::: + +Strapi internals refer to the groups of documents these values select as *publication cohorts*, but you never need that term to use the API. + +## Use cases {#use-cases} | I want to… | Parameters | Full example | | ---------- | ---------- | ------------ | @@ -92,10 +111,6 @@ The table below lists all supported use cases, and the rest of the page shows co | Check whether one specific document matches a value | `publicationFilter` with `findOne()` or `findFirst()` | [Use with findOne() and findFirst()](#find-one-find-first) | | Count only the documents that match a value | `publicationFilter` with `count()` | [Count only matching documents](#count) | -:::note Values ending in -document and localization -Values ending in `-document` consider all locales of a document, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: for example, `never-published-document` excludes a document as soon as one of its locales is published. All other values consider one locale at a time. Without i18n, both variants behave the same. -::: - Pairing a value with the opposite `status` from the table above is valid but returns no rows rather than an error: for example, `never-published` with `status: 'published'` returns an empty result, because these documents have no published row yet. ## Modified documents {#modified} From d307f353997fd57e71d801b93d908878003d0ca3 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:32:38 +0200 Subject: [PATCH 24/75] Trim Modified documents section to a single example The section was the only one with two Endpoint blocks; the draft-vs-published contrast it demonstrated is already covered by two rows in the Use cases table. Keeps the status: 'draft' example and describes the published variant in prose, matching the one-example format of every other section. --- .../document-service/publication-filter.md | 38 +++---------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 06feb0ec08..b8fb086bb9 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -93,7 +93,9 @@ The next section lists all accepted values, [Use cases](#use-cases) maps common Values ending in `-document` consider all locales of a document, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: for example, `never-published-document` excludes a document as soon as one of its locales is published. All other values consider one locale at a time. Without i18n, both variants behave the same. ::: +:::info "Cohorts"? Strapi internals refer to the groups of documents these values select as *publication cohorts*, but you never need that term to use the API. +::: ## Use cases {#use-cases} @@ -111,11 +113,13 @@ Strapi internals refer to the groups of documents these values select as *public | Check whether one specific document matches a value | `publicationFilter` with `findOne()` or `findFirst()` | [Use with findOne() and findFirst()](#find-one-find-first) | | Count only the documents that match a value | `publicationFilter` with `count()` | [Count only matching documents](#count) | +:::caution Pairing a value with the opposite `status` from the table above is valid but returns no rows rather than an error: for example, `never-published` with `status: 'published'` returns an empty result, because these documents have no published row yet. +::: ## Modified documents {#modified} -`modified` selects documents whose draft was edited since it was last published (the draft row's `updatedAt` is more recent than the published row's); `status` then chooses which row you get for those same documents: the newer draft or the currently-live published version. +`modified` selects documents whose draft was edited since it was last published (the draft row's `updatedAt` is more recent than the published row's). The example below returns their newer draft rows; pass `status: 'published'` instead to return the currently-live published version of those same documents. - - ## Documents never published in any locale {#document-scoped} `never-published-document` considers all locales, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: From fbe379954f58e5b7c4de96089cda02df7f6e1820 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:35:01 +0200 Subject: [PATCH 25/75] Align quick example response body indentation with other blocks The quick example response had its body key at 2 spaces and its JSON content at 4 spaces, unlike every other Endpoint block on the page. Normalized to a 6-space key and 2-space content. --- .../document-service/publication-filter.md | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index b8fb086bb9..48d7cb9c37 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -48,8 +48,8 @@ To read the drafts that have never been published, pass: { label: 'JavaScript', code: `await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'never-published', + status: 'draft', + publicationFilter: 'never-published', });` } ]} @@ -57,15 +57,15 @@ To read the drafts that have never been published, pass: { status: 200, statusText: 'OK', - body: `[ - { - documentId: "a1b2c3d4e5f6g7h8i9j0klm", - name: "New Restaurant", - publishedAt: null, - locale: "en", // default locale - // … - } + body: `[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "New Restaurant", + publishedAt: null, + locale: "en", // default locale // … + } + // … ]` } ]} @@ -97,7 +97,7 @@ Values ending in `-document` consider all locales of a document, which matters w Strapi internals refer to the groups of documents these values select as *publication cohorts*, but you never need that term to use the API. ::: -## Use cases {#use-cases} +## Possible use cases {#use-cases} | I want to… | Parameters | Full example | | ---------- | ---------- | ------------ | @@ -117,7 +117,9 @@ Strapi internals refer to the groups of documents these values select as *public Pairing a value with the opposite `status` from the table above is valid but returns no rows rather than an error: for example, `never-published` with `status: 'published'` returns an empty result, because these documents have no published row yet. ::: -## Modified documents {#modified} +## Examples + +### Modified documents {#modified} `modified` selects documents whose draft was edited since it was last published (the draft row's `updatedAt` is more recent than the published row's). The example below returns their newer draft rows; pass `status: 'published'` instead to return the currently-live published version of those same documents. @@ -153,7 +155,7 @@ Pairing a value with the opposite `status` from the table above is valid but ret ]} /> -## Documents never published in any locale {#document-scoped} +### Documents never published in any locale {#document-scoped} `never-published-document` considers all locales, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: @@ -189,7 +191,7 @@ Pairing a value with the opposite `status` from the table above is valid but ret ]} /> -## Published entries without a draft {#published-without-draft-example} +### Published entries without a draft {#published-without-draft-example} `published-without-draft` describes published rows, so it requires `status: 'published'`: @@ -225,7 +227,7 @@ Pairing a value with the opposite `status` from the table above is valid but ret ]} /> -## Published entries with a draft {#published-with-draft-example} +### Published entries with a draft {#published-with-draft-example} `published-with-draft` also describes published rows and requires `status: 'published'`: @@ -261,7 +263,7 @@ Pairing a value with the opposite `status` from the table above is valid but ret ]} /> -## Use with `findOne()` and `findFirst()` {#find-one-find-first} +### Use with `findOne()` and `findFirst()` {#find-one-find-first} If the requested document (and locale, when applicable) does not match the filter, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: @@ -289,7 +291,7 @@ If the requested document (and locale, when applicable) does not match the filte ]} /> -## Count only matching documents {#count} +### Count only matching documents {#count} Without `publicationFilter`, `count({ status: 'draft' })` counts every draft row, including drafts whose document already has a published version. Add `publicationFilter` to count only the documents that match a given value (see the [`status` documentation](/cms/api/document-service/status#count)): @@ -302,10 +304,10 @@ const neverPublishedCount = await strapi }); ``` -## Combine with other parameters {#combine} +### Combine with other parameters {#combine} `publicationFilter` is combined with other query parameters as a logical `AND`, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same filter logic. -## Content Manager mapping {#content-manager} +### Content Manager mapping {#content-manager} In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the per-locale `never-published`). Other Status filter options use internal APIs rather than public `publicationFilter` values. From ae7f09ea75e16925f6c653ca48af10b5fdd286ec Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:37:27 +0200 Subject: [PATCH 26/75] Properly indent quick example again --- .../docs/cms/api/document-service/publication-filter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 48d7cb9c37..642cedc0fa 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -48,8 +48,8 @@ To read the drafts that have never been published, pass: { label: 'JavaScript', code: `await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'never-published', + status: 'draft', + publicationFilter: 'never-published', });` } ]} From 8c437d952b60dfca4e1c09567896715dc16624be Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:38:47 +0200 Subject: [PATCH 27/75] Turn content manager mapping to a simple note --- .../docs/cms/api/document-service/publication-filter.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 642cedc0fa..2d659638e8 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -308,6 +308,6 @@ const neverPublishedCount = await strapi `publicationFilter` is combined with other query parameters as a logical `AND`, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same filter logic. -### Content Manager mapping {#content-manager} - -In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the per-locale `never-published`). Other Status filter options use internal APIs rather than public `publicationFilter` values. +:::note Note: Content Manager mapping +In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the per-locale `never-published`). +::: From 4a6536b07d602f12e45905845112f165ec0ea225 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:40:00 +0200 Subject: [PATCH 28/75] Indent request code bodies to 4 spaces in all Endpoint blocks Applies the same 4-space body indentation as the quick example to the modified, never-published-document, published-without-draft, published-with-draft, and findOne blocks, so every Endpoint request reads consistently. --- .../document-service/publication-filter.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 2d659638e8..763e8bd5eb 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -132,8 +132,8 @@ Pairing a value with the opposite `status` from the table above is valid but ret { label: 'JavaScript', code: `await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'modified', + status: 'draft', + publicationFilter: 'modified', });` } ]} @@ -168,8 +168,8 @@ Pairing a value with the opposite `status` from the table above is valid but ret { label: 'JavaScript', code: `await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'never-published-document', + status: 'draft', + publicationFilter: 'never-published-document', });` } ]} @@ -204,8 +204,8 @@ Pairing a value with the opposite `status` from the table above is valid but ret { label: 'JavaScript', code: `await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'published', - publicationFilter: 'published-without-draft', + status: 'published', + publicationFilter: 'published-without-draft', });` } ]} @@ -240,8 +240,8 @@ Pairing a value with the opposite `status` from the table above is valid but ret { label: 'JavaScript', code: `await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'published', - publicationFilter: 'published-with-draft', + status: 'published', + publicationFilter: 'published-with-draft', });` } ]} @@ -276,9 +276,9 @@ If the requested document (and locale, when applicable) does not match the filte { label: 'JavaScript', code: `await strapi.documents('api::restaurant.restaurant').findOne({ - documentId: 'a1b2c3d4e5f6g7h8i9j0klm', - status: 'draft', - publicationFilter: 'never-published', + documentId: 'a1b2c3d4e5f6g7h8i9j0klm', + status: 'draft', + publicationFilter: 'never-published', });` } ]} From d4548a4449cf3141614bd35368d31ed89e4dad82 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:44:08 +0200 Subject: [PATCH 29/75] Add line break as a test for better table readability --- docusaurus/docs/cms/api/document-service/publication-filter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 763e8bd5eb..85f6713851 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -101,7 +101,7 @@ Strapi internals refer to the groups of documents these values select as *public | I want to… | Parameters | Full example | | ---------- | ---------- | ------------ | -| Find drafts never published in a given locale | `status: 'draft'`, `publicationFilter: 'never-published'` | [Quick example](#quick-example) | +| Find drafts never published in a given locale | `status: 'draft'`
`publicationFilter: 'never-published'` | [Quick example](#quick-example) | | Find the newer drafts of entries modified since their last publication | `status: 'draft'`, `publicationFilter: 'modified'` | [Modified documents](#modified) | | Find the currently-live version of those same modified entries | `status: 'published'`, `publicationFilter: 'modified'` | [Modified documents](#modified) | | Find drafts that have not changed since their last publication | `status: 'draft'` or `'published'`, `publicationFilter: 'unmodified'` | – | From 35ee0ebdafd86ad35f6797236930e9a7827631c4 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:45:59 +0200 Subject: [PATCH 30/75] Put status and publicationFilter on separate lines in use-case table Applies the line-break separator to every use-case row so the two parameter values stack vertically instead of running together on one line, matching the readability test. --- .../api/document-service/publication-filter.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 85f6713851..4b79ede8d9 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -102,14 +102,14 @@ Strapi internals refer to the groups of documents these values select as *public | I want to… | Parameters | Full example | | ---------- | ---------- | ------------ | | Find drafts never published in a given locale | `status: 'draft'`
`publicationFilter: 'never-published'` | [Quick example](#quick-example) | -| Find the newer drafts of entries modified since their last publication | `status: 'draft'`, `publicationFilter: 'modified'` | [Modified documents](#modified) | -| Find the currently-live version of those same modified entries | `status: 'published'`, `publicationFilter: 'modified'` | [Modified documents](#modified) | -| Find drafts that have not changed since their last publication | `status: 'draft'` or `'published'`, `publicationFilter: 'unmodified'` | – | -| Find entries that have both a draft and a published version | `status: 'draft'` or `'published'`, `publicationFilter: 'has-published-version'` | – | -| Find published entries that have no draft counterpart | `status: 'published'`, `publicationFilter: 'published-without-draft'` | [Published entries without a draft](#published-without-draft-example) | -| Find published entries that also have a draft | `status: 'published'`, `publicationFilter: 'published-with-draft'` | [Published entries with a draft](#published-with-draft-example) | -| Find drafts of documents never published in any locale | `status: 'draft'`, `publicationFilter: 'never-published-document'` | [Documents never published in any locale](#document-scoped) | -| Find documents published in at least one locale | `status: 'draft'` or `'published'`, `publicationFilter: 'has-published-version-document'` | – | +| Find the newer drafts of entries modified since their last publication | `status: 'draft'`
`publicationFilter: 'modified'` | [Modified documents](#modified) | +| Find the currently-live version of those same modified entries | `status: 'published'`
`publicationFilter: 'modified'` | [Modified documents](#modified) | +| Find drafts that have not changed since their last publication | `status: 'draft'` or `'published'`
`publicationFilter: 'unmodified'` | – | +| Find entries that have both a draft and a published version | `status: 'draft'` or `'published'`
`publicationFilter: 'has-published-version'` | – | +| Find published entries that have no draft counterpart | `status: 'published'`
`publicationFilter: 'published-without-draft'` | [Published entries without a draft](#published-without-draft-example) | +| Find published entries that also have a draft | `status: 'published'`
`publicationFilter: 'published-with-draft'` | [Published entries with a draft](#published-with-draft-example) | +| Find drafts of documents never published in any locale | `status: 'draft'`
`publicationFilter: 'never-published-document'` | [Documents never published in any locale](#document-scoped) | +| Find documents published in at least one locale | `status: 'draft'` or `'published'`
`publicationFilter: 'has-published-version-document'` | – | | Check whether one specific document matches a value | `publicationFilter` with `findOne()` or `findFirst()` | [Use with findOne() and findFirst()](#find-one-find-first) | | Count only the documents that match a value | `publicationFilter` with `count()` | [Count only matching documents](#count) | From df367842efabe5a018f0c60d63aaa55514bfa3d4 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:48:07 +0200 Subject: [PATCH 31/75] Split use-case table into separate status and publicationFilter columns The single Parameters column becomes two dedicated columns holding just the bare value (draft, never-published), so readers can scan each parameter independently. The findOne and count rows note the method in the goal column and accept any value. --- .../document-service/publication-filter.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 4b79ede8d9..442d3b09f7 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -99,19 +99,19 @@ Strapi internals refer to the groups of documents these values select as *public ## Possible use cases {#use-cases} -| I want to… | Parameters | Full example | -| ---------- | ---------- | ------------ | -| Find drafts never published in a given locale | `status: 'draft'`
`publicationFilter: 'never-published'` | [Quick example](#quick-example) | -| Find the newer drafts of entries modified since their last publication | `status: 'draft'`
`publicationFilter: 'modified'` | [Modified documents](#modified) | -| Find the currently-live version of those same modified entries | `status: 'published'`
`publicationFilter: 'modified'` | [Modified documents](#modified) | -| Find drafts that have not changed since their last publication | `status: 'draft'` or `'published'`
`publicationFilter: 'unmodified'` | – | -| Find entries that have both a draft and a published version | `status: 'draft'` or `'published'`
`publicationFilter: 'has-published-version'` | – | -| Find published entries that have no draft counterpart | `status: 'published'`
`publicationFilter: 'published-without-draft'` | [Published entries without a draft](#published-without-draft-example) | -| Find published entries that also have a draft | `status: 'published'`
`publicationFilter: 'published-with-draft'` | [Published entries with a draft](#published-with-draft-example) | -| Find drafts of documents never published in any locale | `status: 'draft'`
`publicationFilter: 'never-published-document'` | [Documents never published in any locale](#document-scoped) | -| Find documents published in at least one locale | `status: 'draft'` or `'published'`
`publicationFilter: 'has-published-version-document'` | – | -| Check whether one specific document matches a value | `publicationFilter` with `findOne()` or `findFirst()` | [Use with findOne() and findFirst()](#find-one-find-first) | -| Count only the documents that match a value | `publicationFilter` with `count()` | [Count only matching documents](#count) | +| I want to… | `status` | `publicationFilter` | Full example | +| ---------- | -------- | ------------------- | ------------ | +| Find drafts never published in a given locale | `draft` | `never-published` | [Quick example](#quick-example) | +| Find the newer drafts of entries modified since their last publication | `draft` | `modified` | [Modified documents](#modified) | +| Find the currently-live version of those same modified entries | `published` | `modified` | [Modified documents](#modified) | +| Find drafts that have not changed since their last publication | `draft` or `published` | `unmodified` | – | +| Find entries that have both a draft and a published version | `draft` or `published` | `has-published-version` | – | +| Find published entries that have no draft counterpart | `published` | `published-without-draft` | [Published entries without a draft](#published-without-draft-example) | +| Find published entries that also have a draft | `published` | `published-with-draft` | [Published entries with a draft](#published-with-draft-example) | +| Find drafts of documents never published in any locale | `draft` | `never-published-document` | [Documents never published in any locale](#document-scoped) | +| Find documents published in at least one locale | `draft` or `published` | `has-published-version-document` | – | +| Check whether one specific document matches a value (with `findOne()` or `findFirst()`) | `draft` or `published` | any value | [Use with findOne() and findFirst()](#find-one-find-first) | +| Count only the documents that match a value (with `count()`) | `draft` or `published` | any value | [Count only matching documents](#count) | :::caution Pairing a value with the opposite `status` from the table above is valid but returns no rows rather than an error: for example, `never-published` with `status: 'published'` returns an empty result, because these documents have no published row yet. From facdf2cec5f47d05820962c8ea0bf95c08e4022b Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:48:57 +0200 Subject: [PATCH 32/75] Rename use-case columns to Use status with and Use publicationFilter with MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The column headers now read as instructions (Use status with…, Use publicationFilter with…) so each cell value completes the sentence for the row's goal. --- .../docs/cms/api/document-service/publication-filter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 442d3b09f7..c8a166e3a6 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -99,8 +99,8 @@ Strapi internals refer to the groups of documents these values select as *public ## Possible use cases {#use-cases} -| I want to… | `status` | `publicationFilter` | Full example | -| ---------- | -------- | ------------------- | ------------ | +| I want to… | Use `status` with… | Use `publicationFilter` with… | Full example | +| ---------- | ------------------ | ----------------------------- | ------------ | | Find drafts never published in a given locale | `draft` | `never-published` | [Quick example](#quick-example) | | Find the newer drafts of entries modified since their last publication | `draft` | `modified` | [Modified documents](#modified) | | Find the currently-live version of those same modified entries | `published` | `modified` | [Modified documents](#modified) | From a5f7521c4242991a23994239aecbc2d5a33f1c6f Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:50:37 +0200 Subject: [PATCH 33/75] Group use-case rows by status value Reorders the table into three blocks: rows using draft, then published, then draft or published, so readers scanning by status see related goals together. --- .../docs/cms/api/document-service/publication-filter.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index c8a166e3a6..d37234c6ce 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -103,12 +103,12 @@ Strapi internals refer to the groups of documents these values select as *public | ---------- | ------------------ | ----------------------------- | ------------ | | Find drafts never published in a given locale | `draft` | `never-published` | [Quick example](#quick-example) | | Find the newer drafts of entries modified since their last publication | `draft` | `modified` | [Modified documents](#modified) | +| Find drafts of documents never published in any locale | `draft` | `never-published-document` | [Documents never published in any locale](#document-scoped) | | Find the currently-live version of those same modified entries | `published` | `modified` | [Modified documents](#modified) | -| Find drafts that have not changed since their last publication | `draft` or `published` | `unmodified` | – | -| Find entries that have both a draft and a published version | `draft` or `published` | `has-published-version` | – | | Find published entries that have no draft counterpart | `published` | `published-without-draft` | [Published entries without a draft](#published-without-draft-example) | | Find published entries that also have a draft | `published` | `published-with-draft` | [Published entries with a draft](#published-with-draft-example) | -| Find drafts of documents never published in any locale | `draft` | `never-published-document` | [Documents never published in any locale](#document-scoped) | +| Find drafts that have not changed since their last publication | `draft` or `published` | `unmodified` | – | +| Find entries that have both a draft and a published version | `draft` or `published` | `has-published-version` | – | | Find documents published in at least one locale | `draft` or `published` | `has-published-version-document` | – | | Check whether one specific document matches a value (with `findOne()` or `findFirst()`) | `draft` or `published` | any value | [Use with findOne() and findFirst()](#find-one-find-first) | | Count only the documents that match a value (with `count()`) | `draft` or `published` | any value | [Count only matching documents](#count) | From 844940b84488596594b952f3aa89a787606af11c Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:51:39 +0200 Subject: [PATCH 34/75] Use as instead of with in use-case column headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The column headers now read Use status as… and Use publicationFilter as…, since each cell holds the value the parameter takes rather than something used alongside it. --- docusaurus/docs/cms/api/document-service/publication-filter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index d37234c6ce..f8cbfaa151 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -99,7 +99,7 @@ Strapi internals refer to the groups of documents these values select as *public ## Possible use cases {#use-cases} -| I want to… | Use `status` with… | Use `publicationFilter` with… | Full example | +| I want to… | Use `status` as… | Use `publicationFilter` as… | Full example | | ---------- | ------------------ | ----------------------------- | ------------ | | Find drafts never published in a given locale | `draft` | `never-published` | [Quick example](#quick-example) | | Find the newer drafts of entries modified since their last publication | `draft` | `modified` | [Modified documents](#modified) | From 6bf219bf360ab5167a055621e53f2f2699c661ca Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:55:13 +0200 Subject: [PATCH 35/75] Add examples for the three use cases that had none Adds example sections for has-published-version, unmodified, and has-published-version-document, whose behavior was verified against the merged Strapi implementation, and links them from the use-case table instead of the placeholder dash. --- .../document-service/publication-filter.md | 114 +++++++++++++++++- 1 file changed, 111 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index f8cbfaa151..8e20e92ffe 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -107,9 +107,9 @@ Strapi internals refer to the groups of documents these values select as *public | Find the currently-live version of those same modified entries | `published` | `modified` | [Modified documents](#modified) | | Find published entries that have no draft counterpart | `published` | `published-without-draft` | [Published entries without a draft](#published-without-draft-example) | | Find published entries that also have a draft | `published` | `published-with-draft` | [Published entries with a draft](#published-with-draft-example) | -| Find drafts that have not changed since their last publication | `draft` or `published` | `unmodified` | – | -| Find entries that have both a draft and a published version | `draft` or `published` | `has-published-version` | – | -| Find documents published in at least one locale | `draft` or `published` | `has-published-version-document` | – | +| Find drafts that have not changed since their last publication | `draft` or `published` | `unmodified` | [Unmodified entries](#unmodified-example) | +| Find entries that have both a draft and a published version | `draft` or `published` | `has-published-version` | [Entries with a published version](#has-published-version-example) | +| Find documents published in at least one locale | `draft` or `published` | `has-published-version-document` | [Documents published in at least one locale](#has-published-version-document-example) | | Check whether one specific document matches a value (with `findOne()` or `findFirst()`) | `draft` or `published` | any value | [Use with findOne() and findFirst()](#find-one-find-first) | | Count only the documents that match a value (with `count()`) | `draft` or `published` | any value | [Count only matching documents](#count) | @@ -263,6 +263,114 @@ Pairing a value with the opposite `status` from the table above is valid but ret ]} /> +### Entries with a published version {#has-published-version-example} + +`has-published-version` selects documents that have both a draft and a published version for the same locale (it excludes published entries that have no draft counterpart). It returns rows with either `status`; the example below returns the draft rows. + + + +### Unmodified entries {#unmodified-example} + +`unmodified` selects documents whose draft has not changed since it was last published (the draft row's `updatedAt` is not more recent than the published row's). It returns rows with either `status`; the example below returns the draft rows. + + + +### Documents published in at least one locale {#has-published-version-document-example} + +`has-published-version-document` considers all locales, so it matches a document as soon as one of its locales is published. With `status: 'draft'`, it returns the draft rows of every locale of those documents, including locales that were never published themselves: + + + ### Use with `findOne()` and `findFirst()` {#find-one-find-first} If the requested document (and locale, when applicable) does not match the filter, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: From 6ba617290148fabedbd9e51a91ede3d586e799cf Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:56:00 +0200 Subject: [PATCH 36/75] Rewrite publicationFilter intro to lead with its purpose Opens by framing publicationFilter as a companion to status that helps find exactly the documents you need, then contrasts the two questions each parameter answers, before the two-row model paragraph. --- .../docs/cms/api/document-service/publication-filter.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 8e20e92ffe..17b9c708cd 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -24,7 +24,9 @@ Use the optional `publicationFilter` parameter to query documents by the relatio -The [`status`](/cms/api/document-service/status) parameter answers "do I want the draft or the published version?". The `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". For example: drafts that were never published, or entries whose draft has unsaved changes compared to what is live. +The `publicationFilter` is a parameter that, combined with [the `status` parameter](/cms/api/document-service/status), can help you find exactly what you need. + +While `status` answers "do I want the draft or the published version?", the `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". This is useful for example to find drafts that were never published, or entries whose draft has unsaved changes compared to what is live. With Draft & Publish, Strapi stores each entry as up to 2 database rows per locale: a *draft row* and a *published row*. `publicationFilter` selects which documents to consider, based on how these 2 rows relate; `status` picks which of the 2 rows is returned for each of them. The Document Service API returns draft rows when `status` is omitted; REST and GraphQL return published rows instead, so the equivalent queries there need an explicit `status` (see [REST API: `publicationFilter`](/cms/api/rest/publication-filter)). From 59bc1292ec735d82d68f5b3da3bf98761cb53353 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:01:55 +0200 Subject: [PATCH 37/75] Update intro and move quick example --- .../document-service/publication-filter.md | 89 ++++++++++--------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 17b9c708cd..ecfb10f999 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -28,54 +28,14 @@ The `publicationFilter` is a parameter that, combined with [the `status` paramet While `status` answers "do I want the draft or the published version?", the `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". This is useful for example to find drafts that were never published, or entries whose draft has unsaved changes compared to what is live. -With Draft & Publish, Strapi stores each entry as up to 2 database rows per locale: a *draft row* and a *published row*. `publicationFilter` selects which documents to consider, based on how these 2 rows relate; `status` picks which of the 2 rows is returned for each of them. The Document Service API returns draft rows when `status` is omitted; REST and GraphQL return published rows instead, so the equivalent queries there need an explicit `status` (see [REST API: `publicationFilter`](/cms/api/rest/publication-filter)). +:::caution Caution: Different default API behaviors +The Document Service API returns draft versions of documents when `status` is omitted, while REST and GraphQL return the published ones instead, so REST API queries need an explicit `status` (see [REST API: `publicationFilter`](/cms/api/rest/publication-filter)). +::: :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. ::: -## Quick example {#quick-example} - -To read the drafts that have never been published, pass: - -- `status: 'draft'` (so you read the draft row) -- and `publicationFilter: 'never-published'` (so you only keep documents with no published version): - - - -
-The next section lists all accepted values, [Use cases](#use-cases) maps common goals to the parameters to pass, and the rest of the page shows complete examples. - ## Available values {#values} `publicationFilter` accepts one of the following kebab-case values. GraphQL exposes the same set through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). @@ -121,6 +81,49 @@ Pairing a value with the opposite `status` from the table above is valid but ret ## Examples +The following section lists the most common use cases summed up in the [table](#use-cases) above. + +### Find never published drafts + +One of the most common use cases is to find the drafts that have never been published, pass: + +- `status: 'draft'` (so you read the draft row) +- and `publicationFilter: 'never-published'` (so you only keep documents with no published version): + + + +
+ ### Modified documents {#modified} `modified` selects documents whose draft was edited since it was last published (the draft row's `updatedAt` is more recent than the published row's). The example below returns their newer draft rows; pass `status: 'published'` instead to return the currently-live published version of those same documents. From cf423e5903f2c9cca16baea7b1ed9e31ad9c55c8 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:03:20 +0200 Subject: [PATCH 38/75] Repoint use-case table to the relocated never-published example The quick example became the Find never published drafts section under Examples; the table row now links to its new anchor instead of the removed quick-example one. --- .../docs/cms/api/document-service/publication-filter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index ecfb10f999..a88ed80a05 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -63,7 +63,7 @@ Strapi internals refer to the groups of documents these values select as *public | I want to… | Use `status` as… | Use `publicationFilter` as… | Full example | | ---------- | ------------------ | ----------------------------- | ------------ | -| Find drafts never published in a given locale | `draft` | `never-published` | [Quick example](#quick-example) | +| Find drafts never published in a given locale | `draft` | `never-published` | [Find never published drafts](#never-published-example) | | Find the newer drafts of entries modified since their last publication | `draft` | `modified` | [Modified documents](#modified) | | Find drafts of documents never published in any locale | `draft` | `never-published-document` | [Documents never published in any locale](#document-scoped) | | Find the currently-live version of those same modified entries | `published` | `modified` | [Modified documents](#modified) | @@ -83,7 +83,7 @@ Pairing a value with the opposite `status` from the table above is valid but ret The following section lists the most common use cases summed up in the [table](#use-cases) above. -### Find never published drafts +### Find never published drafts {#never-published-example} One of the most common use cases is to find the drafts that have never been published, pass: From f7b65262782645b8fc94cebcf953bf1fe3a8af4a Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:03:44 +0200 Subject: [PATCH 39/75] Fix never published intro --- docusaurus/docs/cms/api/document-service/publication-filter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index a88ed80a05..1425411c97 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -85,7 +85,7 @@ The following section lists the most common use cases summed up in the [table](# ### Find never published drafts {#never-published-example} -One of the most common use cases is to find the drafts that have never been published, pass: +One of the most common use cases is to find the drafts that have never been published. To do so, pass: - `status: 'draft'` (so you read the draft row) - and `publicationFilter: 'never-published'` (so you only keep documents with no published version): From ab8d07dd7e52f0095c3aedf4be256e5c7383ab40 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:08:39 +0200 Subject: [PATCH 40/75] Drop the redundant second sentence introducing the use-case table Keeps the lead-in sentence that introduces the table (required convention) and removes the cookbook-index sentence, whose meaning the first sentence already carries. --- .../docs/cms/api/document-service/publication-filter.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 1425411c97..a05d27e050 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -61,6 +61,8 @@ Strapi internals refer to the groups of documents these values select as *public ## Possible use cases {#use-cases} +The following table lists many possible use cases, illustrating how the `status` and `publicationFilter` parameters can be combined to find exactly what you need with the Document Service API: + | I want to… | Use `status` as… | Use `publicationFilter` as… | Full example | | ---------- | ------------------ | ----------------------------- | ------------ | | Find drafts never published in a given locale | `draft` | `never-published` | [Find never published drafts](#never-published-example) | @@ -124,7 +126,7 @@ One of the most common use cases is to find the drafts that have never been publ
-### Modified documents {#modified} +### Find modified documents {#modified} `modified` selects documents whose draft was edited since it was last published (the draft row's `updatedAt` is more recent than the published row's). The example below returns their newer draft rows; pass `status: 'published'` instead to return the currently-live published version of those same documents. @@ -160,7 +162,7 @@ One of the most common use cases is to find the drafts that have never been publ ]} /> -### Documents never published in any locale {#document-scoped} +### Find documents never published in any locale {#document-scoped} `never-published-document` considers all locales, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: From 37ad681980d284b27c457f16f6fb0625ccb3933a Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:20:19 +0200 Subject: [PATCH 41/75] Show both status variants in the modified documents example Simplifies the intro to drop the updatedAt implementation detail, then gives modified its own draft and published examples with distinct responses so the status contrast is shown, not just described. --- .../document-service/publication-filter.md | 70 ++++++++++++++----- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index a05d27e050..2352782d9f 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -24,11 +24,11 @@ Use the optional `publicationFilter` parameter to query documents by the relatio -The `publicationFilter` is a parameter that, combined with [the `status` parameter](/cms/api/document-service/status), can help you find exactly what you need. +The `publicationFilter` is a parameter that, combined with [the `status` parameter](/cms/api/document-service/status), can help you cover complex queries to find exactly what you need with the [Document Service API](/cms/api/document-service). While `status` answers "do I want the draft or the published version?", the `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". This is useful for example to find drafts that were never published, or entries whose draft has unsaved changes compared to what is live. -:::caution Caution: Different default API behaviors +:::caution Caution: Different default behaviors for different APIs The Document Service API returns draft versions of documents when `status` is omitted, while REST and GraphQL return the published ones instead, so REST API queries need an explicit `status` (see [REST API: `publicationFilter`](/cms/api/rest/publication-filter)). ::: @@ -38,7 +38,7 @@ The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled o ## Available values {#values} -`publicationFilter` accepts one of the following kebab-case values. GraphQL exposes the same set through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). +`publicationFilter` accepts one of the following kebab-case values. | Value | Selects | | ----- | ------- | @@ -49,16 +49,18 @@ The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled o | `published-without-draft` | Published entries with no draft counterpart | | `published-with-draft` | Published entries that also have a draft | | `never-published-document` | Documents never published in any locale | -| `has-published-version-document` | Documents published in at least one locale | - -:::note Values ending in -document and localization -Values ending in `-document` consider all locales of a document, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: for example, `never-published-document` excludes a document as soon as one of its locales is published. All other values consider one locale at a time. Without i18n, both variants behave the same. -::: +| `has-published-version-document` | Documents published in at least one locale
(useful when [i18n](/cms/features/internationalization) is enabled) | :::info "Cohorts"? Strapi internals refer to the groups of documents these values select as *publication cohorts*, but you never need that term to use the API. ::: +:::note Notes +* GraphQL exposes the same set through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). +* Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). +* Values ending in `-document` consider all locales of a document, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: for example, `never-published-document` excludes a document as soon as one of its locales is published. All other values consider one locale at a time. Without i18n, both variants behave the same. +::: + ## Possible use cases {#use-cases} The following table lists many possible use cases, illustrating how the `status` and `publicationFilter` parameters can be combined to find exactly what you need with the Document Service API: @@ -128,13 +130,15 @@ One of the most common use cases is to find the drafts that have never been publ ### Find modified documents {#modified} -`modified` selects documents whose draft was edited since it was last published (the draft row's `updatedAt` is more recent than the published row's). The example below returns their newer draft rows; pass `status: 'published'` instead to return the currently-live published version of those same documents. +`publicationFilter: modified` selects documents whose draft has unpublished changes. `status` then decides which version of those documents you get back. + +With `status: 'draft'`, the query returns the pending draft rows: +With `status: 'published'`, the same query returns the currently live version of those documents instead: + + + ### Find documents never published in any locale {#document-scoped} -`never-published-document` considers all locales, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: +`publicationFilter: never-published-document` considers all locales, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: -### Published entries without a draft {#published-without-draft-example} +### Find published entries without a draft {#published-without-draft-example} `published-without-draft` describes published rows, so it requires `status: 'published'`: @@ -234,7 +272,7 @@ One of the most common use cases is to find the drafts that have never been publ ]} /> -### Published entries with a draft {#published-with-draft-example} +### Find published entries with a draft {#published-with-draft-example} `published-with-draft` also describes published rows and requires `status: 'published'`: @@ -270,7 +308,7 @@ One of the most common use cases is to find the drafts that have never been publ ]} /> -### Entries with a published version {#has-published-version-example} +### Find entries with a published version {#has-published-version-example} `has-published-version` selects documents that have both a draft and a published version for the same locale (it excludes published entries that have no draft counterpart). It returns rows with either `status`; the example below returns the draft rows. @@ -306,7 +344,7 @@ One of the most common use cases is to find the drafts that have never been publ ]} /> -### Unmodified entries {#unmodified-example} +### Find unmodified entries {#unmodified-example} `unmodified` selects documents whose draft has not changed since it was last published (the draft row's `updatedAt` is not more recent than the published row's). It returns rows with either `status`; the example below returns the draft rows. @@ -342,7 +380,7 @@ One of the most common use cases is to find the drafts that have never been publ ]} /> -### Documents published in at least one locale {#has-published-version-document-example} +### Find documents published in at least one locale {#has-published-version-document-example} `has-published-version-document` considers all locales, so it matches a document as soon as one of its locales is published. With `status: 'draft'`, it returns the draft rows of every locale of those documents, including locales that were never published themselves: From a4c98a8808ef19a9c63f4260f7e38a392ace159e Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:21:31 +0200 Subject: [PATCH 42/75] Prefix example intros with publicationFilter: before the value The published-without-draft, published-with-draft, has-published-version, unmodified, and has-published-version-document intros now read publicationFilter: instead of the bare value, so the sentence names the parameter. --- .../cms/api/document-service/publication-filter.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 2352782d9f..893bce212b 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -238,7 +238,7 @@ With `status: 'published'`, the same query returns the currently live version of ### Find published entries without a draft {#published-without-draft-example} -`published-without-draft` describes published rows, so it requires `status: 'published'`: +`publicationFilter: published-without-draft` describes published rows, so it requires `status: 'published'`: Date: Fri, 3 Jul 2026 10:43:04 +0200 Subject: [PATCH 43/75] Align example order with the use-case table order Both the use-case table and the example sections now follow one order grouped by status (draft, then published, then draft or published): never-published, never-published-document, modified, published-without-draft, published-with-draft, unmodified, has-published-version, has-published-version-document. Also fixes the table link labels to match the section titles. --- .../document-service/publication-filter.md | 104 +++++++++--------- 1 file changed, 50 insertions(+), 54 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 893bce212b..19a4efa791 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -28,17 +28,13 @@ The `publicationFilter` is a parameter that, combined with [the `status` paramet While `status` answers "do I want the draft or the published version?", the `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". This is useful for example to find drafts that were never published, or entries whose draft has unsaved changes compared to what is live. -:::caution Caution: Different default behaviors for different APIs -The Document Service API returns draft versions of documents when `status` is omitted, while REST and GraphQL return the published ones instead, so REST API queries need an explicit `status` (see [REST API: `publicationFilter`](/cms/api/rest/publication-filter)). -::: - :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. ::: ## Available values {#values} -`publicationFilter` accepts one of the following kebab-case values. +`publicationFilter` accepts one of the following values: | Value | Selects | | ----- | ------- | @@ -51,16 +47,16 @@ The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled o | `never-published-document` | Documents never published in any locale | | `has-published-version-document` | Documents published in at least one locale
(useful when [i18n](/cms/features/internationalization) is enabled) | -:::info "Cohorts"? -Strapi internals refer to the groups of documents these values select as *publication cohorts*, but you never need that term to use the API. -::: - :::note Notes -* GraphQL exposes the same set through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). -* Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). +* Unknown values raise a validation error. * Values ending in `-document` consider all locales of a document, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: for example, `never-published-document` excludes a document as soon as one of its locales is published. All other values consider one locale at a time. Without i18n, both variants behave the same. ::: +:::caution Caution: Different default behaviors for different APIs +The Document Service API returns draft versions of documents when `status` is omitted, while REST and GraphQL return the published ones instead, so REST API queries need an explicit `status` (see [REST API: `publicationFilter`](/cms/api/rest/publication-filter)). +::: + + ## Possible use cases {#use-cases} The following table lists many possible use cases, illustrating how the `status` and `publicationFilter` parameters can be combined to find exactly what you need with the Document Service API: @@ -68,14 +64,14 @@ The following table lists many possible use cases, illustrating how the `status` | I want to… | Use `status` as… | Use `publicationFilter` as… | Full example | | ---------- | ------------------ | ----------------------------- | ------------ | | Find drafts never published in a given locale | `draft` | `never-published` | [Find never published drafts](#never-published-example) | -| Find the newer drafts of entries modified since their last publication | `draft` | `modified` | [Modified documents](#modified) | -| Find drafts of documents never published in any locale | `draft` | `never-published-document` | [Documents never published in any locale](#document-scoped) | -| Find the currently-live version of those same modified entries | `published` | `modified` | [Modified documents](#modified) | -| Find published entries that have no draft counterpart | `published` | `published-without-draft` | [Published entries without a draft](#published-without-draft-example) | -| Find published entries that also have a draft | `published` | `published-with-draft` | [Published entries with a draft](#published-with-draft-example) | -| Find drafts that have not changed since their last publication | `draft` or `published` | `unmodified` | [Unmodified entries](#unmodified-example) | -| Find entries that have both a draft and a published version | `draft` or `published` | `has-published-version` | [Entries with a published version](#has-published-version-example) | -| Find documents published in at least one locale | `draft` or `published` | `has-published-version-document` | [Documents published in at least one locale](#has-published-version-document-example) | +| Find drafts of documents never published in any locale | `draft` | `never-published-document` | [Find documents never published in any locale](#document-scoped) | +| Find the newer drafts of entries modified since their last publication | `draft` | `modified` | [Find modified documents](#modified) | +| Find the currently-live version of those same modified entries | `published` | `modified` | [Find modified documents](#modified) | +| Find published entries that have no draft counterpart | `published` | `published-without-draft` | [Find published entries without a draft](#published-without-draft-example) | +| Find published entries that also have a draft | `published` | `published-with-draft` | [Find published entries with a draft](#published-with-draft-example) | +| Find drafts that have not changed since their last publication | `draft` or `published` | `unmodified` | [Find unmodified entries](#unmodified-example) | +| Find entries that have both a draft and a published version | `draft` or `published` | `has-published-version` | [Find entries with a published version](#has-published-version-example) | +| Find documents published in at least one locale | `draft` or `published` | `has-published-version-document` | [Find documents published in at least one locale](#has-published-version-document-example) | | Check whether one specific document matches a value (with `findOne()` or `findFirst()`) | `draft` or `published` | any value | [Use with findOne() and findFirst()](#find-one-find-first) | | Count only the documents that match a value (with `count()`) | `draft` or `published` | any value | [Count only matching documents](#count) | @@ -128,23 +124,21 @@ One of the most common use cases is to find the drafts that have never been publ
-### Find modified documents {#modified} - -`publicationFilter: modified` selects documents whose draft has unpublished changes. `status` then decides which version of those documents you get back. +### Find documents never published in any locale {#document-scoped} -With `status: 'draft'`, the query returns the pending draft rows: +`publicationFilter: never-published-document` considers all locales, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: -With `status: 'published'`, the same query returns the currently live version of those documents instead: +### Find modified documents {#modified} + +`publicationFilter: modified` selects documents whose draft has unpublished changes. `status` then decides which version of those documents you get back. + +With `status: 'draft'`, the query returns the pending draft rows: -### Find documents never published in any locale {#document-scoped} - -`publicationFilter: never-published-document` considers all locales, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: +With `status: 'published'`, the same query returns the currently live version of those documents instead: -### Find entries with a published version {#has-published-version-example} +### Find unmodified entries {#unmodified-example} -`publicationFilter: has-published-version` selects documents that have both a draft and a published version for the same locale (it excludes published entries that have no draft counterpart). It returns rows with either `status`; the example below returns the draft rows. +`publicationFilter: unmodified` selects documents whose draft has not changed since it was last published (the draft row's `updatedAt` is not more recent than the published row's). It returns rows with either `status`; the example below returns the draft rows. -### Find unmodified entries {#unmodified-example} +### Find entries with a published version {#has-published-version-example} -`publicationFilter: unmodified` selects documents whose draft has not changed since it was last published (the draft row's `updatedAt` is not more recent than the published row's). It returns rows with either `status`; the example below returns the draft rows. +`publicationFilter: has-published-version` selects documents that have both a draft and a published version for the same locale (it excludes published entries that have no draft counterpart). It returns rows with either `status`; the example below returns the draft rows. Date: Fri, 3 Jul 2026 10:46:35 +0200 Subject: [PATCH 44/75] Turn use-case goals into links and drop the redundant example column The Full example column duplicated the goal column, so it is removed and each goal in the first column becomes a link to its example section, with link text matching the section title exactly. A lead-in line tells readers to click a use case for the example. --- .../document-service/publication-filter.md | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 19a4efa791..af7798b972 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -59,21 +59,21 @@ The Document Service API returns draft versions of documents when `status` is om ## Possible use cases {#use-cases} -The following table lists many possible use cases, illustrating how the `status` and `publicationFilter` parameters can be combined to find exactly what you need with the Document Service API: - -| I want to… | Use `status` as… | Use `publicationFilter` as… | Full example | -| ---------- | ------------------ | ----------------------------- | ------------ | -| Find drafts never published in a given locale | `draft` | `never-published` | [Find never published drafts](#never-published-example) | -| Find drafts of documents never published in any locale | `draft` | `never-published-document` | [Find documents never published in any locale](#document-scoped) | -| Find the newer drafts of entries modified since their last publication | `draft` | `modified` | [Find modified documents](#modified) | -| Find the currently-live version of those same modified entries | `published` | `modified` | [Find modified documents](#modified) | -| Find published entries that have no draft counterpart | `published` | `published-without-draft` | [Find published entries without a draft](#published-without-draft-example) | -| Find published entries that also have a draft | `published` | `published-with-draft` | [Find published entries with a draft](#published-with-draft-example) | -| Find drafts that have not changed since their last publication | `draft` or `published` | `unmodified` | [Find unmodified entries](#unmodified-example) | -| Find entries that have both a draft and a published version | `draft` or `published` | `has-published-version` | [Find entries with a published version](#has-published-version-example) | -| Find documents published in at least one locale | `draft` or `published` | `has-published-version-document` | [Find documents published in at least one locale](#has-published-version-document-example) | -| Check whether one specific document matches a value (with `findOne()` or `findFirst()`) | `draft` or `published` | any value | [Use with findOne() and findFirst()](#find-one-find-first) | -| Count only the documents that match a value (with `count()`) | `draft` or `published` | any value | [Count only matching documents](#count) | +The following table lists many possible use cases, illustrating how the `status` and `publicationFilter` parameters can be combined to find exactly what you need with the Document Service API. Click a use case to jump to a complete example: + +| I want to… | Use `status` as… | Use `publicationFilter` as… | +| ---------- | ------------------ | ----------------------------- | +| [Find never published drafts](#never-published-example) | `draft` | `never-published` | +| [Find documents never published in any locale](#document-scoped) | `draft` | `never-published-document` | +| [Find modified documents](#modified) | `draft` | `modified` | +| [Find modified documents](#modified) | `published` | `modified` | +| [Find published entries without a draft](#published-without-draft-example) | `published` | `published-without-draft` | +| [Find published entries with a draft](#published-with-draft-example) | `published` | `published-with-draft` | +| [Find unmodified entries](#unmodified-example) | `draft` or `published` | `unmodified` | +| [Find entries with a published version](#has-published-version-example) | `draft` or `published` | `has-published-version` | +| [Find documents published in at least one locale](#has-published-version-document-example) | `draft` or `published` | `has-published-version-document` | +| [Use with `findOne()` and `findFirst()`](#find-one-find-first) | `draft` or `published` | any value | +| [Count only matching documents](#count) | `draft` or `published` | any value | :::caution Pairing a value with the opposite `status` from the table above is valid but returns no rows rather than an error: for example, `never-published` with `status: 'published'` returns an empty result, because these documents have no published row yet. From 0e0a690acb3c8e93b773bcae1a08c8ab4fd0b16d Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:48:06 +0200 Subject: [PATCH 45/75] Merge "modified" lines --- docusaurus/docs/cms/api/document-service/publication-filter.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index af7798b972..4f9f456413 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -65,8 +65,7 @@ The following table lists many possible use cases, illustrating how the `status` | ---------- | ------------------ | ----------------------------- | | [Find never published drafts](#never-published-example) | `draft` | `never-published` | | [Find documents never published in any locale](#document-scoped) | `draft` | `never-published-document` | -| [Find modified documents](#modified) | `draft` | `modified` | -| [Find modified documents](#modified) | `published` | `modified` | +| [Find modified documents](#modified) | `draft` or `published` | `modified` | | [Find published entries without a draft](#published-without-draft-example) | `published` | `published-without-draft` | | [Find published entries with a draft](#published-with-draft-example) | `published` | `published-with-draft` | | [Find unmodified entries](#unmodified-example) | `draft` or `published` | `unmodified` | From 704cbd7a64fc9a5c4dad137f64fbc1627995688f Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:50:50 +0200 Subject: [PATCH 46/75] Further refine table --- .../cms/api/document-service/publication-filter.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 4f9f456413..c74ad1e334 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -66,16 +66,16 @@ The following table lists many possible use cases, illustrating how the `status` | [Find never published drafts](#never-published-example) | `draft` | `never-published` | | [Find documents never published in any locale](#document-scoped) | `draft` | `never-published-document` | | [Find modified documents](#modified) | `draft` or `published` | `modified` | -| [Find published entries without a draft](#published-without-draft-example) | `published` | `published-without-draft` | -| [Find published entries with a draft](#published-with-draft-example) | `published` | `published-with-draft` | -| [Find unmodified entries](#unmodified-example) | `draft` or `published` | `unmodified` | -| [Find entries with a published version](#has-published-version-example) | `draft` or `published` | `has-published-version` | +| [Find unmodified documents](#unmodified-example) | `draft` or `published` | `unmodified` | +| [Find published documents without a draft](#published-without-draft-example) | `published` | `published-without-draft` | +| [Find published documents with a draft](#published-with-draft-example) | `published` | `published-with-draft` | +| [Find documents with a published version](#has-published-version-example) | `draft` or `published` | `has-published-version` | | [Find documents published in at least one locale](#has-published-version-document-example) | `draft` or `published` | `has-published-version-document` | | [Use with `findOne()` and `findFirst()`](#find-one-find-first) | `draft` or `published` | any value | | [Count only matching documents](#count) | `draft` or `published` | any value | :::caution -Pairing a value with the opposite `status` from the table above is valid but returns no rows rather than an error: for example, `never-published` with `status: 'published'` returns an empty result, because these documents have no published row yet. +Pairing a value with the opposite `status` from the table above is valid but returns nothing rather than an error: for example, `never-published` with `status: 'published'` returns an empty result, because these documents have no published counterpart yet. ::: ## Examples From 9b7f8588c15cde1a5d892fd6610ed1e1c4991189 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:53:06 +0200 Subject: [PATCH 47/75] Realign example sections with the edited use-case table After manual table edits, the example H3 titles and their order had drifted: renames the four entries titles to documents to match the table link text, and moves the unmodified section up to sit after modified, so table and examples share one order and identical labels. --- .../document-service/publication-filter.md | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index c74ad1e334..aa9a2297cd 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -231,21 +231,21 @@ With `status: 'published'`, the same query returns the currently live version of ]} /> -### Find published entries without a draft {#published-without-draft-example} +### Find unmodified documents {#unmodified-example} -`publicationFilter: published-without-draft` describes published rows, so it requires `status: 'published'`: +`publicationFilter: unmodified` selects documents whose draft has not changed since it was last published (the draft row's `updatedAt` is not more recent than the published row's). It returns rows with either `status`; the example below returns the draft rows. -### Find published entries with a draft {#published-with-draft-example} +### Find published documents without a draft {#published-without-draft-example} -`publicationFilter: published-with-draft` also describes published rows and requires `status: 'published'`: +`publicationFilter: published-without-draft` describes published rows, so it requires `status: 'published'`: -### Find unmodified entries {#unmodified-example} +### Find published documents with a draft {#published-with-draft-example} -`publicationFilter: unmodified` selects documents whose draft has not changed since it was last published (the draft row's `updatedAt` is not more recent than the published row's). It returns rows with either `status`; the example below returns the draft rows. +`publicationFilter: published-with-draft` also describes published rows and requires `status: 'published'`: -### Find entries with a published version {#has-published-version-example} +### Find documents with a published version {#has-published-version-example} `publicationFilter: has-published-version` selects documents that have both a draft and a published version for the same locale (it excludes published entries that have no draft counterpart). It returns rows with either `status`; the example below returns the draft rows. From 45df93666d92c1913d15bbf0eb5f6b72b1957acb Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:57:17 +0200 Subject: [PATCH 48/75] Simplify custom anchors --- .../document-service/publication-filter.md | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index aa9a2297cd..cf8fcde21f 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -64,7 +64,7 @@ The following table lists many possible use cases, illustrating how the `status` | I want to… | Use `status` as… | Use `publicationFilter` as… | | ---------- | ------------------ | ----------------------------- | | [Find never published drafts](#never-published-example) | `draft` | `never-published` | -| [Find documents never published in any locale](#document-scoped) | `draft` | `never-published-document` | +| [Find drafts never published in any locale](#document-scoped) | `draft` | `never-published-document` | | [Find modified documents](#modified) | `draft` or `published` | `modified` | | [Find unmodified documents](#unmodified-example) | `draft` or `published` | `unmodified` | | [Find published documents without a draft](#published-without-draft-example) | `published` | `published-without-draft` | @@ -82,9 +82,11 @@ Pairing a value with the opposite `status` from the table above is valid but ret The following section lists the most common use cases summed up in the [table](#use-cases) above. -### Find never published drafts {#never-published-example} +### Find never published drafts {#never-published} -One of the most common use cases is to find the drafts that have never been published. To do so, pass: +One of the most common use cases is to find the drafts that have never been published. To do so, pass `status: 'draft'` and `publicationFilter: 'never-published'`. + +This parameter combination works only on a given locale; to find these documents across all locales, [use `never-published-document`](#never-published-document) instead. - `status: 'draft'` (so you read the draft row) - and `publicationFilter: 'never-published'` (so you only keep documents with no published version): @@ -123,7 +125,7 @@ One of the most common use cases is to find the drafts that have never been publ
-### Find documents never published in any locale {#document-scoped} +### Find drafts never published in any locale {#never-published-document} `publicationFilter: never-published-document` considers all locales, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: @@ -231,7 +233,7 @@ With `status: 'published'`, the same query returns the currently live version of ]} /> -### Find unmodified documents {#unmodified-example} +### Find unmodified documents {#unmodified} `publicationFilter: unmodified` selects documents whose draft has not changed since it was last published (the draft row's `updatedAt` is not more recent than the published row's). It returns rows with either `status`; the example below returns the draft rows. @@ -267,7 +269,7 @@ With `status: 'published'`, the same query returns the currently live version of ]} /> -### Find published documents without a draft {#published-without-draft-example} +### Find published documents without a draft {#published-without-draft} `publicationFilter: published-without-draft` describes published rows, so it requires `status: 'published'`: @@ -303,7 +305,7 @@ With `status: 'published'`, the same query returns the currently live version of ]} /> -### Find published documents with a draft {#published-with-draft-example} +### Find published documents with a draft {#published-with-draft} `publicationFilter: published-with-draft` also describes published rows and requires `status: 'published'`: @@ -339,7 +341,7 @@ With `status: 'published'`, the same query returns the currently live version of ]} /> -### Find documents with a published version {#has-published-version-example} +### Find documents with a published version {#has-published-version} `publicationFilter: has-published-version` selects documents that have both a draft and a published version for the same locale (it excludes published entries that have no draft counterpart). It returns rows with either `status`; the example below returns the draft rows. @@ -375,7 +377,7 @@ With `status: 'published'`, the same query returns the currently live version of ]} /> -### Find documents published in at least one locale {#has-published-version-document-example} +### Find documents published in at least one locale {#has-published-version-document} `publicationFilter: has-published-version-document` considers all locales, so it matches a document as soon as one of its locales is published. With `status: 'draft'`, it returns the draft rows of every locale of those documents, including locales that were never published themselves: @@ -457,5 +459,5 @@ const neverPublishedCount = await strapi `publicationFilter` is combined with other query parameters as a logical `AND`, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same filter logic. :::note Note: Content Manager mapping -In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the per-locale `never-published`). +In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the per-locale `never-published`). ::: From a087fcc6092517fd819bc5c52428d3be58e0697a Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:59:33 +0200 Subject: [PATCH 49/75] Fix use-case table links after anchor renames The section anchors were renamed (dropped the -example suffix, document-scoped became never-published-document); the seven use-case table links now point to the new anchors. Inbound links from the REST and GraphQL pages target #values, which is unchanged. --- .../cms/api/document-service/publication-filter.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index cf8fcde21f..8aac8121c8 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -63,14 +63,14 @@ The following table lists many possible use cases, illustrating how the `status` | I want to… | Use `status` as… | Use `publicationFilter` as… | | ---------- | ------------------ | ----------------------------- | -| [Find never published drafts](#never-published-example) | `draft` | `never-published` | -| [Find drafts never published in any locale](#document-scoped) | `draft` | `never-published-document` | +| [Find never published drafts](#never-published) | `draft` | `never-published` | +| [Find drafts never published in any locale](#never-published-document) | `draft` | `never-published-document` | | [Find modified documents](#modified) | `draft` or `published` | `modified` | -| [Find unmodified documents](#unmodified-example) | `draft` or `published` | `unmodified` | -| [Find published documents without a draft](#published-without-draft-example) | `published` | `published-without-draft` | -| [Find published documents with a draft](#published-with-draft-example) | `published` | `published-with-draft` | -| [Find documents with a published version](#has-published-version-example) | `draft` or `published` | `has-published-version` | -| [Find documents published in at least one locale](#has-published-version-document-example) | `draft` or `published` | `has-published-version-document` | +| [Find unmodified documents](#unmodified) | `draft` or `published` | `unmodified` | +| [Find published documents without a draft](#published-without-draft) | `published` | `published-without-draft` | +| [Find published documents with a draft](#published-with-draft) | `published` | `published-with-draft` | +| [Find documents with a published version](#has-published-version) | `draft` or `published` | `has-published-version` | +| [Find documents published in at least one locale](#has-published-version-document) | `draft` or `published` | `has-published-version-document` | | [Use with `findOne()` and `findFirst()`](#find-one-find-first) | `draft` or `published` | any value | | [Count only matching documents](#count) | `draft` or `published` | any value | From 4c5485cf48ef91b289d56b52dc2c0b9bfa985ef3 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:06:10 +0200 Subject: [PATCH 50/75] Replace row(s) with version(s) + other formatting stuff --- .../document-service/publication-filter.md | 65 +++++++++---------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 8aac8121c8..154efa6f53 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -20,7 +20,7 @@ tags: -Use the optional `publicationFilter` parameter to query documents by the relationship between their draft and published versions, for example drafts that were never published, or entries modified since they were last published. It works with `findOne()`, `findFirst()`, `findMany()`, and `count()`, and combines with other query parameters. `status` still decides whether you get the draft or the published row. +Use the optional `publicationFilter` parameter to query documents by the relationship between their draft and published versions, for example drafts that were never published, or entries modified since they were last published. It works with `findOne()`, `findFirst()`, `findMany()`, and `count()`, and combines with other query parameters. `status` still decides whether you get the draft or the published version. @@ -56,7 +56,6 @@ The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled o The Document Service API returns draft versions of documents when `status` is omitted, while REST and GraphQL return the published ones instead, so REST API queries need an explicit `status` (see [REST API: `publicationFilter`](/cms/api/rest/publication-filter)). ::: - ## Possible use cases {#use-cases} The following table lists many possible use cases, illustrating how the `status` and `publicationFilter` parameters can be combined to find exactly what you need with the Document Service API. Click a use case to jump to a complete example: @@ -88,9 +87,6 @@ One of the most common use cases is to find the drafts that have never been publ This parameter combination works only on a given locale; to find these documents across all locales, [use `never-published-document`](#never-published-document) instead. -- `status: 'draft'` (so you read the draft row) -- and `publicationFilter: 'never-published'` (so you only keep documents with no published version): - -
- ### Find drafts never published in any locale {#never-published-document} `publicationFilter: never-published-document` considers all locales, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: @@ -165,13 +159,13 @@ This parameter combination works only on a given locale; to find these documents `publicationFilter: modified` selects documents whose draft has unpublished changes. `status` then decides which version of those documents you get back. -With `status: 'draft'`, the query returns the pending draft rows: +With `status: 'draft'`, the query returns the pending draft versions: +
With `status: 'published'`, the same query returns the currently live version of those documents instead: Date: Fri, 3 Jul 2026 11:07:24 +0200 Subject: [PATCH 51/75] Simplify the never-published-document explanation into shorter sentences The single dense sentence with a double negative becomes three: what the value returns, that it looks at the whole document across locales, and the concrete consequence that one published locale excludes the whole document. --- .../api/document-service/publication-filter.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 154efa6f53..0755fa6340 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -121,7 +121,9 @@ This parameter combination works only on a given locale; to find these documents ### Find drafts never published in any locale {#never-published-document} -`publicationFilter: never-published-document` considers all locales, so a multi-locale document with even one published locale is excluded entirely, including its draft-only locales: +`publicationFilter: never-published-document` returns documents that have never been published in any locale. It looks at the whole document across all its locales, not one locale at a time. + +As a result, a document counts as published as soon as one of its locales is published: the document is then left out, even the locales that only exist as a draft. The example below returns the draft rows of documents that were never published anywhere: Date: Fri, 3 Jul 2026 11:09:13 +0200 Subject: [PATCH 52/75] Further formatting and clarification on modified docs --- .../document-service/publication-filter.md | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 0755fa6340..b80e314f6e 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -161,7 +161,7 @@ As a result, a document counts as published as soon as one of its locales is pub `publicationFilter: modified` selects documents whose draft has unpublished changes. `status` then decides which version of those documents you get back. -With `status: 'draft'`, the query returns the pending draft versions: +For instance, with `status: 'draft'`, the query returns the pending draft versions: Date: Fri, 3 Jul 2026 11:12:43 +0200 Subject: [PATCH 53/75] Parallelize unmodified example and add intros to published-without/with-draft unmodified now shows both status: 'draft' and status: 'published' examples, matching the modified section. The two published-* sections gain a lead-in sentence stating what the value selects, like the other example sections. --- .../document-service/publication-filter.md | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index b80e314f6e..39f8a06cf0 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -232,12 +232,14 @@ With `status: 'published'`, the same query returns the currently live version of ### Find unmodified documents {#unmodified} -`publicationFilter: unmodified` selects documents whose draft has not changed since it was last published. It returns entries with either `status`; the following example returns the draft versions: +`publicationFilter: unmodified` selects documents whose draft has not changed since it was last published. `status` then decides which version of those documents you get back. + +For instance, with `status: 'draft'`, the query returns the draft versions: +
+With `status: 'published'`, the same query returns the currently live version of those documents instead: + + + ### Find published documents without a draft {#published-without-draft} -`publicationFilter: published-without-draft` must be paired with `status: 'published'`: +`publicationFilter: published-without-draft` selects published documents that have no draft counterpart. It describes published rows, so it must be paired with `status: 'published'`: Date: Fri, 3 Jul 2026 11:13:52 +0200 Subject: [PATCH 54/75] Show both status variants in the has-published-version example Replaces the either-status prose with a draft and a published example, matching the modified and unmodified sections: draft returns the pending version, published returns the currently live one. --- .../document-service/publication-filter.md | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 39f8a06cf0..023b9932de 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -377,12 +377,14 @@ With `status: 'published'`, the same query returns the currently live version of ### Find documents with a published version {#has-published-version} -`publicationFilter: has-published-version` selects documents that have both a draft and a published version for the same locale (it excludes published entries that have no draft counterpart). It returns versions with either `status`; the example below returns the draft versions. +`publicationFilter: has-published-version` selects documents that have both a draft and a published version for the same locale (it excludes published entries that have no draft counterpart). `status` then decides which version of those documents you get back. + +For instance, with `status: 'draft'`, the query returns the draft versions: + +
+With `status: 'published'`, the same query returns the currently live version of those documents instead: + + Date: Fri, 3 Jul 2026 11:29:18 +0200 Subject: [PATCH 55/75] Further refinements: descriptions, formatting, hierarchy --- .../document-service/publication-filter.md | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 023b9932de..e4387f19ee 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -47,6 +47,8 @@ The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled o | `never-published-document` | Documents never published in any locale | | `has-published-version-document` | Documents published in at least one locale
(useful when [i18n](/cms/features/internationalization) is enabled) | +For detailed examples of how to use the `publicationFilter` values, including with the `status` parameter, see the [possible use cases](#use-cases) table. + :::note Notes * Unknown values raise a validation error. * Values ending in `-document` consider all locales of a document, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: for example, `never-published-document` excludes a document as soon as one of its locales is published. All other values consider one locale at a time. Without i18n, both variants behave the same. @@ -73,8 +75,8 @@ The following table lists many possible use cases, illustrating how the `status` | [Use with `findOne()` and `findFirst()`](#find-one-find-first) | `draft` or `published` | any value | | [Count only matching documents](#count) | `draft` or `published` | any value | -:::caution -Pairing a value with the opposite `status` from the table above is valid but returns nothing rather than an error: for example, `never-published` with `status: 'published'` returns an empty result, because these documents have no published counterpart yet. +:::note +Pairing a value with the opposite `status` from the table above is valid but returns nothing rather than an error: for example, `never-published` with `status: 'published'` returns an empty result, because these documents have no published version yet. ::: ## Examples @@ -121,9 +123,9 @@ This parameter combination works only on a given locale; to find these documents ### Find drafts never published in any locale {#never-published-document} -`publicationFilter: never-published-document` returns documents that have never been published in any locale. It looks at the whole document across all its locales, not one locale at a time. +`publicationFilter: never-published-document` returns documents that have never been published in any locale. It looks at the whole document across all its locales, not one locale at a time. To find these documents for a given locale only, [use `never-published`](#never-published) instead. -As a result, a document counts as published as soon as one of its locales is published: the document is then left out, even the locales that only exist as a draft. The example below returns the draft rows of documents that were never published anywhere: +A document counts as published as soon as one of its locales is published: the document is then left out, even the locales that only exist as a draft. The example below returns the draft rows of documents that were never published anywhere: +:::note Note: Content Manager mapping +In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the per-locale `never-published`). +::: + ### Find modified documents {#modified} -`publicationFilter: modified` selects documents whose draft has unpublished changes. `status` then decides which version of those documents you get back. +`publicationFilter: modified` selects documents whose draft has modified but unpublished changes. `status` then decides which version of those documents you get back. For instance, with `status: 'draft'`, the query returns the pending draft versions: @@ -305,7 +311,9 @@ With `status: 'published'`, the same query returns the currently live version of ### Find published documents without a draft {#published-without-draft} -`publicationFilter: published-without-draft` selects published documents that have no draft counterpart. It describes published rows, so it must be paired with `status: 'published'`: +`publicationFilter: published-without-draft` selects published documents that have no draft counterpart. + +`published-without-draft` must be paired with `status: 'published'`: Date: Fri, 3 Jul 2026 11:33:40 +0200 Subject: [PATCH 56/75] Fix terminology and parallelism issues from the review Standardizes published entries to published documents and the lone draft rows to draft versions, removes the doubled Note labels on two admonitions, aligns the has-published-version intro with the modified and unmodified sections, and normalizes the Endpoint descriptions in the three two-status sections. --- .../document-service/publication-filter.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index e4387f19ee..a3e6fb5e55 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -42,14 +42,14 @@ The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled o | `has-published-version` | Documents that have both a draft and a published version | | `modified` | Documents whose draft was edited since it was last published | | `unmodified` | Documents whose draft has not changed since it was last published | -| `published-without-draft` | Published entries with no draft counterpart | -| `published-with-draft` | Published entries that also have a draft | +| `published-without-draft` | Published documents with no draft counterpart | +| `published-with-draft` | Published documents that also have a draft | | `never-published-document` | Documents never published in any locale | | `has-published-version-document` | Documents published in at least one locale
(useful when [i18n](/cms/features/internationalization) is enabled) | For detailed examples of how to use the `publicationFilter` values, including with the `status` parameter, see the [possible use cases](#use-cases) table. -:::note Notes +:::note * Unknown values raise a validation error. * Values ending in `-document` consider all locales of a document, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: for example, `never-published-document` excludes a document as soon as one of its locales is published. All other values consider one locale at a time. Without i18n, both variants behave the same. ::: @@ -125,7 +125,7 @@ This parameter combination works only on a given locale; to find these documents `publicationFilter: never-published-document` returns documents that have never been published in any locale. It looks at the whole document across all its locales, not one locale at a time. To find these documents for a given locale only, [use `never-published`](#never-published) instead. -A document counts as published as soon as one of its locales is published: the document is then left out, even the locales that only exist as a draft. The example below returns the draft rows of documents that were never published anywhere: +A document counts as published as soon as one of its locales is published: the document is then left out, even the locales that only exist as a draft. The example below returns the draft versions of documents that were never published anywhere: -:::note Note: Content Manager mapping +:::note Content Manager mapping In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the per-locale `never-published`). ::: @@ -167,13 +167,13 @@ In the Content Manager, the **Draft (never published)** list filter maps to `sta `publicationFilter: modified` selects documents whose draft has modified but unpublished changes. `status` then decides which version of those documents you get back. -For instance, with `status: 'draft'`, the query returns the pending draft versions: +For instance, with `status: 'draft'`, the query returns the draft versions: Date: Fri, 3 Jul 2026 11:39:11 +0200 Subject: [PATCH 57/75] Align Available values table order with the use-case and example order The reference table now lists the eight values in the same sequence as the use-case table and the example sections, so a reader scanning the page top to bottom sees one consistent order. --- .../docs/cms/api/document-service/publication-filter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index a3e6fb5e55..4773da703a 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -39,12 +39,12 @@ The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled o | Value | Selects | | ----- | ------- | | `never-published` | Documents never published in a given locale | -| `has-published-version` | Documents that have both a draft and a published version | +| `never-published-document` | Documents never published in any locale | | `modified` | Documents whose draft was edited since it was last published | | `unmodified` | Documents whose draft has not changed since it was last published | | `published-without-draft` | Published documents with no draft counterpart | | `published-with-draft` | Published documents that also have a draft | -| `never-published-document` | Documents never published in any locale | +| `has-published-version` | Documents that have both a draft and a published version | | `has-published-version-document` | Documents published in at least one locale
(useful when [i18n](/cms/features/internationalization) is enabled) | For detailed examples of how to use the `publicationFilter` values, including with the `status` parameter, see the [possible use cases](#use-cases) table. From 4953971c830e27672a4e327c08566be1975c5433 Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Fri, 3 Jul 2026 11:40:25 +0200 Subject: [PATCH 58/75] Apply suggestion from @pwizla --- docusaurus/docs/cms/api/document-service/status.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/api/document-service/status.md b/docusaurus/docs/cms/api/document-service/status.md index e4b3aae5cd..5a305e6641 100644 --- a/docusaurus/docs/cms/api/document-service/status.md +++ b/docusaurus/docs/cms/api/document-service/status.md @@ -153,7 +153,7 @@ const publishedCount = await strapi.documents("api::restaurant.restaurant").coun :::note Since published documents necessarily also have a draft counterpart, a published document is still counted as having a draft version. -This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. To count only never-published drafts, pass a [`publicationFilter`](/cms/api/document-service/publication-filter) value such as `'never-published'` or `'never-published-document'`. +This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. To count only never-published drafts, [pass a `publicationFilter` value](/cms/api/document-service/publication-filter) such as `'never-published'` or `'never-published-document'`. ::: ## Create a draft and publish it {#create} From ecc9a450d1ce8af6dac54a93cca2e21da8253a7c Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:45:55 +0200 Subject: [PATCH 59/75] Rewrite REST publicationFilter page to match the Document Service structure Applies the same flat structure as the Document Service page: compact intro, Available values table, a caution about the REST published default, a linked use-case table, and one example section per value with parallel wording. modified, unmodified, and has-published-version each show both status variants. All examples use the REST Endpoint component and are verified against the merged Strapi implementation. --- .../docs/cms/api/rest/publication-filter.md | 508 ++++++++++++++++-- 1 file changed, 473 insertions(+), 35 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 7ce4dff5fe..bd5b5b0cc7 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -20,34 +20,72 @@ tags: -Add the optional `publicationFilter` query parameter to filter results by the relationship between a document's draft and published versions, for example never-published or modified entries. The [`status`](/cms/api/rest/status) parameter still selects whether each result returns its draft or published row. REST defaults to `status=published`, so pass `status=draft` for values that only match drafts. +Add the optional `publicationFilter` query parameter to query documents by the relationship between their draft and published versions, for example documents that were never published, or documents modified since they were last published. It combines with other query parameters, and `status` still decides whether you get the draft or the published version. -The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. `publicationFilter` filters results by the relationship between a document's draft and published versions, for example entries never published, or entries modified since they were last published. +The `publicationFilter` is a query parameter that, combined with [the `status` parameter](/cms/api/rest/status), can help you cover complex queries to find exactly what you need with the [REST API](/cms/api/rest). -:::note Key terms -Strapi Draft & Publish stores each entry as up to 2 database rows for the same document and locale: +While `status` answers "do I want the draft or the published version?", the `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". This is useful for example to find documents that were never published, or documents whose draft has unsaved changes compared to what is live. -- a *draft row* (`publishedAt` is empty) -- a *published row* (`publishedAt` is set) +:::prerequisites +The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. +::: + +## Available values {#values} + +`publicationFilter` accepts one of the following values: + +| Value | Selects | +| ----- | ------- | +| `never-published` | Documents never published in a given locale | +| `never-published-document` | Documents never published in any locale | +| `modified` | Documents whose draft was edited since it was last published | +| `unmodified` | Documents whose draft has not changed since it was last published | +| `published-without-draft` | Published documents with no draft counterpart | +| `published-with-draft` | Published documents that also have a draft | +| `has-published-version` | Documents that have both a draft and a published version | +| `has-published-version-document` | Documents published in at least one locale
(useful when [i18n](/cms/features/internationalization) is enabled) | + +For detailed examples of how to use the `publicationFilter` values, including with the `status` parameter, see the [possible use cases](#use-cases) table. -The [`status`](/cms/api/rest/status) parameter picks which of the 2 rows to read. +:::note +* Unknown values return an HTTP `400` error. +* Values ending in `-document` consider all locales of a document, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: for example, `never-published-document` excludes a document as soon as one of its locales is published. All other values consider one locale at a time. Without i18n, both variants behave the same. +::: -`publicationFilter` instead selects a group of documents based on how their draft and published rows relate (for example, never published, or draft newer than published). Some of these questions compare the 2 rows, so they cannot be expressed by filtering on `publishedAt` alone. +:::caution Caution: Different default behaviors for different APIs +The REST API returns published versions of documents when `status` is omitted, so queries for draft-only values such as `never-published` need an explicit `status=draft`. The Document Service API returns draft versions instead (see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter)). ::: -`publicationFilter` selects the group of documents first; `status` then decides which row (draft or published) is returned for each result. One rule explains most surprises on this page: when `status` is omitted, REST defaults to `status=published` **before** applying `publicationFilter`, so values that only match drafts, such as `never-published`, return empty results unless you pass `status=draft`. [Understand the default `status`](#default-status) details each combination. +## Possible use cases {#use-cases} -This page shows how to use the most common `publicationFilter` values over REST. For the full list of values, their exact definitions, and every `status` combination, see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter), which is the reference for the underlying model. +The following table lists many possible use cases, illustrating how the `status` and `publicationFilter` parameters can be combined to find exactly what you need with the REST API. Click a use case to jump to a complete example: -:::prerequisites -The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. +| I want to… | Use `status` as… | Use `publicationFilter` as… | +| ---------- | ------------------ | ----------------------------- | +| [Find never published drafts](#never-published) | `draft` | `never-published` | +| [Find drafts never published in any locale](#never-published-document) | `draft` | `never-published-document` | +| [Find modified documents](#modified) | `draft` or `published` | `modified` | +| [Find unmodified documents](#unmodified) | `draft` or `published` | `unmodified` | +| [Find published documents without a draft](#published-without-draft) | `published` | `published-without-draft` | +| [Find published documents with a draft](#published-with-draft) | `published` | `published-with-draft` | +| [Find documents with a published version](#has-published-version) | `draft` or `published` | `has-published-version` | +| [Find documents published in at least one locale](#has-published-version-document) | `draft` or `published` | `has-published-version-document` | + +:::note +Pairing a value with the opposite `status` from the table above is valid but returns nothing rather than an error: for example, `never-published` with `status=published` returns an empty result, because these documents have no published version yet. ::: -## Get never-published draft documents {#never-published} +## Examples + +The following section lists the most common use cases summed up in the [table](#use-cases) above. + +### Find never published drafts {#never-published} + +One of the most common use cases is to find the drafts that have never been published. To do so, pass `status=draft` and `publicationFilter=never-published`. -`never-published` matches documents with no published version for that locale, so only draft rows exist. Pass `status=draft`, because REST defaults to `status=published`: +This parameter combination works only on a given locale; to find these documents across all locales, [use `never-published-document`](#never-published-document) instead. -## Get modified documents {#modified} +### Find drafts never published in any locale {#never-published-document} + +`publicationFilter=never-published-document` returns documents that have never been published in any locale. It looks at the whole document across all its locales, not one locale at a time. To find these documents for a given locale only, [use `never-published`](#never-published) instead. + +A document counts as published as soon as one of its locales is published: the document is then left out, even the locales that only exist as a draft. The example below returns the draft versions of documents that were never published anywhere: + + + +### Find modified documents {#modified} + +`publicationFilter=modified` selects documents whose draft has modified but unpublished changes. `status` then decides which version of those documents you get back. + +For instance, with `status=draft`, the query returns the draft versions: + + -`modified` matches documents whose draft is newer than their published version. With no `status` parameter, REST returns the **published** rows of those documents. Pass `status=draft` to return the newer draft rows instead: +
+With `status=published` (the REST default), the same query returns the currently live version of those documents instead: + +### Find unmodified documents {#unmodified} + +`publicationFilter=unmodified` selects documents whose draft has not changed since it was last published. `status` then decides which version of those documents you get back. + +For instance, with `status=draft`, the query returns the draft versions: + + + +
+With `status=published` (the REST default), the same query returns the currently live version of those documents instead: + + -## Get published documents without a draft {#published-without-draft} +### Find published documents without a draft {#published-without-draft} -`published-without-draft` matches published rows that have no draft for the same `(documentId, locale)`. REST defaults to `status=published`, so you can omit `status`: +`publicationFilter=published-without-draft` selects published documents that have no draft counterpart. It describes published rows, so REST returns them with the default `status=published`: -## Understand the default `status` {#default-status} +### Find published documents with a draft {#published-with-draft} + +`publicationFilter=published-with-draft` selects published documents that also have a draft. It describes published rows, so REST returns them with the default `status=published`: + + + +### Find documents with a published version {#has-published-version} + +`publicationFilter=has-published-version` selects documents that have both a draft and a published version for the same locale. `status` then decides which version of those documents you get back. Unlike `published-without-draft`, it excludes published documents that have no draft counterpart. + +For instance, with `status=draft`, the query returns the draft versions: + + + +
+With `status=published` (the REST default), the same query returns the currently live version of those documents instead: -When `status` is omitted, REST defaults to `status=published` **before** applying `publicationFilter`. This is the most common source of unexpected empty results: a value that only matches drafts, such as `never-published`, returns nothing under the default status. The table below shows the effect for the values used above: + -The Document Service API defaults to `status=draft` instead (see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter)). +### Find documents published in at least one locale {#has-published-version-document} -## Reference: accepted values {#values} +`publicationFilter=has-published-version-document` considers all locales, so it matches a document as soon as one of its locales is published. With `status=draft`, it returns the draft versions of every locale of those documents, including locales that were never published themselves: -REST accepts these kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. Invalid values return HTTP `400`. + ## Combine with other parameters {#combine} `publicationFilter` can be combined with [`filters`](/cms/api/rest/filters), [`locale`](/cms/api/rest/locale), [`populate`](/cms/api/rest/populate-select), and other [REST parameters](/cms/api/rest/parameters). All conditions are applied together. + +For the full list of values, their exact definitions, and every `status` combination, see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter), which is the reference for the underlying model. From 5b1d1f76bee77b3ac2fdffc9a178e52a665957f0 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:51:34 +0200 Subject: [PATCH 60/75] Drop redundant REST codeTab from publicationFilter Endpoints The Endpoint component already renders the method and path as a header and URL bar, so the extra REST tab whose code repeated GET /api/... showed the request twice. Each Endpoint now keeps only the JavaScript tab, matching the other REST parameter pages. --- .../docs/cms/api/rest/publication-filter.md | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index bd5b5b0cc7..9277e0a177 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -92,10 +92,6 @@ This parameter combination works only on a given locale; to find these documents path="/api/restaurants?status=draft&publicationFilter=never-published" title="Get draft restaurants that have never been published for their locale" codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?status=draft&publicationFilter=never-published` - }, { label: "JavaScript", code: `const qs = require('qs'); @@ -146,10 +142,6 @@ A document counts as published as soon as one of its locales is published: the d path="/api/restaurants?status=draft&publicationFilter=never-published-document" title="Get drafts of restaurants never published in any locale" codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?status=draft&publicationFilter=never-published-document` - }, { label: "JavaScript", code: `const qs = require('qs'); @@ -200,10 +192,6 @@ For instance, with `status=draft`, the query returns the draft versions: path="/api/restaurants?status=draft&publicationFilter=modified" title="Get the draft versions of modified restaurants" codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?status=draft&publicationFilter=modified` - }, { label: "JavaScript", code: `const qs = require('qs'); @@ -251,10 +239,6 @@ With `status=published` (the REST default), the same query returns the currently path="/api/restaurants?publicationFilter=modified" title="Get the currently live version of modified restaurants" codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?publicationFilter=modified` - }, { label: "JavaScript", code: `const qs = require('qs'); @@ -304,10 +288,6 @@ For instance, with `status=draft`, the query returns the draft versions: path="/api/restaurants?status=draft&publicationFilter=unmodified" title="Get the draft versions of unmodified restaurants" codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?status=draft&publicationFilter=unmodified` - }, { label: "JavaScript", code: `const qs = require('qs'); @@ -355,10 +335,6 @@ With `status=published` (the REST default), the same query returns the currently path="/api/restaurants?publicationFilter=unmodified" title="Get the currently live version of unmodified restaurants" codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?publicationFilter=unmodified` - }, { label: "JavaScript", code: `const qs = require('qs'); @@ -406,10 +382,6 @@ await request(\`/api/restaurants?\${query}\`);` path="/api/restaurants?publicationFilter=published-without-draft" title="Get published restaurants with no draft for the same locale" codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?publicationFilter=published-without-draft` - }, { label: "JavaScript", code: `const qs = require('qs'); @@ -457,10 +429,6 @@ await request(\`/api/restaurants?\${query}\`);` path="/api/restaurants?publicationFilter=published-with-draft" title="Get published restaurants that also have a draft for the same locale" codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?publicationFilter=published-with-draft` - }, { label: "JavaScript", code: `const qs = require('qs'); @@ -510,10 +478,6 @@ For instance, with `status=draft`, the query returns the draft versions: path="/api/restaurants?status=draft&publicationFilter=has-published-version" title="Get the draft versions of restaurants that also have a published version" codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?status=draft&publicationFilter=has-published-version` - }, { label: "JavaScript", code: `const qs = require('qs'); @@ -561,10 +525,6 @@ With `status=published` (the REST default), the same query returns the currently path="/api/restaurants?publicationFilter=has-published-version" title="Get the currently live version of restaurants that also have a draft" codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?publicationFilter=has-published-version` - }, { label: "JavaScript", code: `const qs = require('qs'); @@ -612,10 +572,6 @@ await request(\`/api/restaurants?\${query}\`);` path="/api/restaurants?status=draft&publicationFilter=has-published-version-document" title="Get the drafts of restaurants published in at least one locale" codeTabs={[ - { - label: "REST", - code: `GET /api/restaurants?status=draft&publicationFilter=has-published-version-document` - }, { label: "JavaScript", code: `const qs = require('qs'); From 97218520b022163cffdb75f04b866790d01b4f96 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:54:21 +0200 Subject: [PATCH 61/75] Restore missing Endpoint descriptions on REST publicationFilter page The description prop was dropped from all ten Endpoints during the rewrite, so no description rendered under the method and path header. Each Endpoint gets its description back, parallel to the Document Service page. --- docusaurus/docs/cms/api/rest/publication-filter.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 9277e0a177..0a84838863 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -91,6 +91,7 @@ This parameter combination works only on a given locale; to find these documents method="GET" path="/api/restaurants?status=draft&publicationFilter=never-published" title="Get draft restaurants that have never been published for their locale" + description="Return the drafts that have never been published for their locale." codeTabs={[ { label: "JavaScript", @@ -141,6 +142,7 @@ A document counts as published as soon as one of its locales is published: the d method="GET" path="/api/restaurants?status=draft&publicationFilter=never-published-document" title="Get drafts of restaurants never published in any locale" + description="Return the drafts of documents never published in any locale." codeTabs={[ { label: "JavaScript", @@ -191,6 +193,7 @@ For instance, with `status=draft`, the query returns the draft versions: method="GET" path="/api/restaurants?status=draft&publicationFilter=modified" title="Get the draft versions of modified restaurants" + description="Return the draft versions of documents with unpublished changes." codeTabs={[ { label: "JavaScript", @@ -238,6 +241,7 @@ With `status=published` (the REST default), the same query returns the currently method="GET" path="/api/restaurants?publicationFilter=modified" title="Get the currently live version of modified restaurants" + description="Return the currently live versions of documents with unpublished changes." codeTabs={[ { label: "JavaScript", @@ -287,6 +291,7 @@ For instance, with `status=draft`, the query returns the draft versions: method="GET" path="/api/restaurants?status=draft&publicationFilter=unmodified" title="Get the draft versions of unmodified restaurants" + description="Return the draft versions of documents unchanged since their last publication." codeTabs={[ { label: "JavaScript", @@ -334,6 +339,7 @@ With `status=published` (the REST default), the same query returns the currently method="GET" path="/api/restaurants?publicationFilter=unmodified" title="Get the currently live version of unmodified restaurants" + description="Return the currently live versions of documents unchanged since their last publication." codeTabs={[ { label: "JavaScript", @@ -381,6 +387,7 @@ await request(\`/api/restaurants?\${query}\`);` method="GET" path="/api/restaurants?publicationFilter=published-without-draft" title="Get published restaurants with no draft for the same locale" + description="Return published documents with no matching draft version for the same locale." codeTabs={[ { label: "JavaScript", @@ -428,6 +435,7 @@ await request(\`/api/restaurants?\${query}\`);` method="GET" path="/api/restaurants?publicationFilter=published-with-draft" title="Get published restaurants that also have a draft for the same locale" + description="Return published documents that also have a matching draft version for the same locale." codeTabs={[ { label: "JavaScript", @@ -477,6 +485,7 @@ For instance, with `status=draft`, the query returns the draft versions: method="GET" path="/api/restaurants?status=draft&publicationFilter=has-published-version" title="Get the draft versions of restaurants that also have a published version" + description="Return the draft versions of documents that also have a published version for the same locale." codeTabs={[ { label: "JavaScript", @@ -524,6 +533,7 @@ With `status=published` (the REST default), the same query returns the currently method="GET" path="/api/restaurants?publicationFilter=has-published-version" title="Get the currently live version of restaurants that also have a draft" + description="Return the currently live versions of documents that also have a published version for the same locale." codeTabs={[ { label: "JavaScript", @@ -571,6 +581,7 @@ await request(\`/api/restaurants?\${query}\`);` method="GET" path="/api/restaurants?status=draft&publicationFilter=has-published-version-document" title="Get the drafts of restaurants published in at least one locale" + description="Return the draft versions of documents published in at least one locale." codeTabs={[ { label: "JavaScript", From ebbb7cd81e7b689a3e579445f232b6c5581a8c58 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:58:09 +0200 Subject: [PATCH 62/75] Add cURL tab before JavaScript on REST publicationFilter Endpoints Follows the filters.md convention: each Endpoint keeps a bare /api/restaurants path and lists a cURL tab (GET /api/restaurants?...) before the JavaScript tab. Moving the query string out of path into the cURL tab also removes the earlier duplicated-request rendering. --- .../docs/cms/api/rest/publication-filter.md | 66 +++++++++++++++---- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 0a84838863..865b6aa5f8 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -89,10 +89,14 @@ This parameter combination works only on a given locale; to find these documents Date: Fri, 3 Jul 2026 12:00:26 +0200 Subject: [PATCH 63/75] Use real curl commands in REST publicationFilter cURL tabs Matches the rest.md format: the cURL tab now holds a curl 'http://localhost:1337/api/...' command with the Authorization header, instead of the GET /api/... shorthand. --- .../docs/cms/api/rest/publication-filter.md | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 865b6aa5f8..216709e912 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -95,7 +95,8 @@ This parameter combination works only on a given locale; to find these documents codeTabs={[ { label: "cURL", - code: `GET /api/restaurants?status=draft&publicationFilter=never-published` + code: `curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=never-published' \\ + -H 'Authorization: Bearer '` }, { label: "JavaScript", @@ -150,7 +151,8 @@ A document counts as published as soon as one of its locales is published: the d codeTabs={[ { label: "cURL", - code: `GET /api/restaurants?status=draft&publicationFilter=never-published-document` + code: `curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=never-published-document' \\ + -H 'Authorization: Bearer '` }, { label: "JavaScript", @@ -205,7 +207,8 @@ For instance, with `status=draft`, the query returns the draft versions: codeTabs={[ { label: "cURL", - code: `GET /api/restaurants?status=draft&publicationFilter=modified` + code: `curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=modified' \\ + -H 'Authorization: Bearer '` }, { label: "JavaScript", @@ -257,7 +260,8 @@ With `status=published` (the REST default), the same query returns the currently codeTabs={[ { label: "cURL", - code: `GET /api/restaurants?publicationFilter=modified` + code: `curl 'http://localhost:1337/api/restaurants?publicationFilter=modified' \\ + -H 'Authorization: Bearer '` }, { label: "JavaScript", @@ -311,7 +315,8 @@ For instance, with `status=draft`, the query returns the draft versions: codeTabs={[ { label: "cURL", - code: `GET /api/restaurants?status=draft&publicationFilter=unmodified` + code: `curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=unmodified' \\ + -H 'Authorization: Bearer '` }, { label: "JavaScript", @@ -363,7 +368,8 @@ With `status=published` (the REST default), the same query returns the currently codeTabs={[ { label: "cURL", - code: `GET /api/restaurants?publicationFilter=unmodified` + code: `curl 'http://localhost:1337/api/restaurants?publicationFilter=unmodified' \\ + -H 'Authorization: Bearer '` }, { label: "JavaScript", @@ -415,7 +421,8 @@ await request(\`/api/restaurants?\${query}\`);` codeTabs={[ { label: "cURL", - code: `GET /api/restaurants?publicationFilter=published-without-draft` + code: `curl 'http://localhost:1337/api/restaurants?publicationFilter=published-without-draft' \\ + -H 'Authorization: Bearer '` }, { label: "JavaScript", @@ -467,7 +474,8 @@ await request(\`/api/restaurants?\${query}\`);` codeTabs={[ { label: "cURL", - code: `GET /api/restaurants?publicationFilter=published-with-draft` + code: `curl 'http://localhost:1337/api/restaurants?publicationFilter=published-with-draft' \\ + -H 'Authorization: Bearer '` }, { label: "JavaScript", @@ -521,7 +529,8 @@ For instance, with `status=draft`, the query returns the draft versions: codeTabs={[ { label: "cURL", - code: `GET /api/restaurants?status=draft&publicationFilter=has-published-version` + code: `curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=has-published-version' \\ + -H 'Authorization: Bearer '` }, { label: "JavaScript", @@ -573,7 +582,8 @@ With `status=published` (the REST default), the same query returns the currently codeTabs={[ { label: "cURL", - code: `GET /api/restaurants?publicationFilter=has-published-version` + code: `curl 'http://localhost:1337/api/restaurants?publicationFilter=has-published-version' \\ + -H 'Authorization: Bearer '` }, { label: "JavaScript", @@ -625,7 +635,8 @@ await request(\`/api/restaurants?\${query}\`);` codeTabs={[ { label: "cURL", - code: `GET /api/restaurants?status=draft&publicationFilter=has-published-version-document` + code: `curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=has-published-version-document' \\ + -H 'Authorization: Bearer '` }, { label: "JavaScript", From 9c0a41b011efb0dc2894dcbc78f8246fc68c0a24 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:00:26 +0200 Subject: [PATCH 64/75] Add a cURL tab to the REST status example The status page Endpoint had only a JavaScript tab; it now leads with a cURL tab using the rest.md curl-command format, consistent with the other REST pages. --- docusaurus/docs/cms/api/rest/status.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/api/rest/status.md b/docusaurus/docs/cms/api/rest/status.md index 425e8ef59a..f1d4ff4484 100644 --- a/docusaurus/docs/cms/api/rest/status.md +++ b/docusaurus/docs/cms/api/rest/status.md @@ -54,10 +54,15 @@ To select documents by how their draft and published versions relate (never-publ '`, + }, { label: 'JavaScript', code: `const qs = require('qs'); From df4e9e6b79c8ee850c4f458201eb4bbd88f6c7d9 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:02:39 +0200 Subject: [PATCH 65/75] Use proper indentation --- .../docs/cms/api/rest/publication-filter.md | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 216709e912..746877520d 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -102,10 +102,10 @@ This parameter combination works only on a given locale; to find these documents label: "JavaScript", code: `const qs = require('qs'); const query = qs.stringify({ - status: 'draft', - publicationFilter: 'never-published', -}, { - encodeValuesOnly: true, // prettify URL + status: 'draft', + publicationFilter: 'never-published', + }, { + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`);` @@ -116,22 +116,22 @@ await request(\`/api/restaurants?\${query}\`);` status: 200, statusText: "OK", body: `{ - "data": [ - { - "documentId": "a1b2c3d4e5f6g7h8i9j0klm", - "name": "New Restaurant", - "publishedAt": null, - "locale": "en" + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "New Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 - } - } }` } ]} From 3bf9d6944d7d7dafa92ea5c14944caed68359e24 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:07:26 +0200 Subject: [PATCH 66/75] Apply proper code indentation to remaining REST publicationFilter examples Extends the indentation fix to every example: the qs.stringify object body and the JSON response body are indented two more spaces, matching the first example. The cURL command and its header line keep their two-space continuation. --- .../docs/cms/api/rest/publication-filter.md | 370 +++++++++--------- 1 file changed, 185 insertions(+), 185 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 746877520d..4472eeae20 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -158,10 +158,10 @@ A document counts as published as soon as one of its locales is published: the d label: "JavaScript", code: `const qs = require('qs'); const query = qs.stringify({ - status: 'draft', - publicationFilter: 'never-published-document', + status: 'draft', + publicationFilter: 'never-published-document', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`);` @@ -172,22 +172,22 @@ await request(\`/api/restaurants?\${query}\`);` status: 200, statusText: "OK", body: `{ - "data": [ - { - "documentId": "d41r46wac4xix5vpba7561at", - "name": "New Restaurant", - "publishedAt": null, - "locale": "en" - } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 - } - } + "data": [ + { + "documentId": "d41r46wac4xix5vpba7561at", + "name": "New Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } }` } ]} @@ -214,10 +214,10 @@ For instance, with `status=draft`, the query returns the draft versions: label: "JavaScript", code: `const qs = require('qs'); const query = qs.stringify({ - status: 'draft', - publicationFilter: 'modified', + status: 'draft', + publicationFilter: 'modified', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`);` @@ -228,22 +228,22 @@ await request(\`/api/restaurants?\${query}\`);` status: 200, statusText: "OK", body: `{ - "data": [ - { - "documentId": "a1b2c3d4e5f6g7h8i9j0klm", - "name": "Biscotte Restaurant (updated)", - "publishedAt": null, - "locale": "en" - } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 - } - } + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant (updated)", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } }` } ]} @@ -267,9 +267,9 @@ With `status=published` (the REST default), the same query returns the currently label: "JavaScript", code: `const qs = require('qs'); const query = qs.stringify({ - publicationFilter: 'modified', + publicationFilter: 'modified', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`);` @@ -280,22 +280,22 @@ await request(\`/api/restaurants?\${query}\`);` status: 200, statusText: "OK", body: `{ - "data": [ - { - "documentId": "a1b2c3d4e5f6g7h8i9j0klm", - "name": "Biscotte Restaurant", - "publishedAt": "2024-03-14T15:40:45.330Z", - "locale": "en" - } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 - } - } + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } }` } ]} @@ -322,10 +322,10 @@ For instance, with `status=draft`, the query returns the draft versions: label: "JavaScript", code: `const qs = require('qs'); const query = qs.stringify({ - status: 'draft', - publicationFilter: 'unmodified', + status: 'draft', + publicationFilter: 'unmodified', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`);` @@ -336,22 +336,22 @@ await request(\`/api/restaurants?\${query}\`);` status: 200, statusText: "OK", body: `{ - "data": [ - { - "documentId": "a1b2c3d4e5f6g7h8i9j0klm", - "name": "Biscotte Restaurant", - "publishedAt": null, - "locale": "en" - } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 - } - } + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } }` } ]} @@ -375,9 +375,9 @@ With `status=published` (the REST default), the same query returns the currently label: "JavaScript", code: `const qs = require('qs'); const query = qs.stringify({ - publicationFilter: 'unmodified', + publicationFilter: 'unmodified', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`);` @@ -388,22 +388,22 @@ await request(\`/api/restaurants?\${query}\`);` status: 200, statusText: "OK", body: `{ - "data": [ - { - "documentId": "a1b2c3d4e5f6g7h8i9j0klm", - "name": "Biscotte Restaurant", - "publishedAt": "2024-03-14T15:40:45.330Z", - "locale": "en" - } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 - } - } + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } }` } ]} @@ -428,9 +428,9 @@ await request(\`/api/restaurants?\${query}\`);` label: "JavaScript", code: `const qs = require('qs'); const query = qs.stringify({ - publicationFilter: 'published-without-draft', + publicationFilter: 'published-without-draft', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`);` @@ -441,22 +441,22 @@ await request(\`/api/restaurants?\${query}\`);` status: 200, statusText: "OK", body: `{ - "data": [ - { - "documentId": "j0klm1n2o3p4q5r6s7t8u9v", - "name": "Legacy Restaurant", - "publishedAt": "2024-01-10T09:15:00.000Z", - "locale": "en" - } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 - } - } + "data": [ + { + "documentId": "j0klm1n2o3p4q5r6s7t8u9v", + "name": "Legacy Restaurant", + "publishedAt": "2024-01-10T09:15:00.000Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } }` } ]} @@ -481,9 +481,9 @@ await request(\`/api/restaurants?\${query}\`);` label: "JavaScript", code: `const qs = require('qs'); const query = qs.stringify({ - publicationFilter: 'published-with-draft', + publicationFilter: 'published-with-draft', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`);` @@ -494,22 +494,22 @@ await request(\`/api/restaurants?\${query}\`);` status: 200, statusText: "OK", body: `{ - "data": [ - { - "documentId": "a1b2c3d4e5f6g7h8i9j0klm", - "name": "Biscotte Restaurant", - "publishedAt": "2024-03-14T15:40:45.330Z", - "locale": "en" - } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 - } - } + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } }` } ]} @@ -536,10 +536,10 @@ For instance, with `status=draft`, the query returns the draft versions: label: "JavaScript", code: `const qs = require('qs'); const query = qs.stringify({ - status: 'draft', - publicationFilter: 'has-published-version', + status: 'draft', + publicationFilter: 'has-published-version', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`);` @@ -550,22 +550,22 @@ await request(\`/api/restaurants?\${query}\`);` status: 200, statusText: "OK", body: `{ - "data": [ - { - "documentId": "a1b2c3d4e5f6g7h8i9j0klm", - "name": "Biscotte Restaurant", - "publishedAt": null, - "locale": "en" - } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 - } - } + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } }` } ]} @@ -589,9 +589,9 @@ With `status=published` (the REST default), the same query returns the currently label: "JavaScript", code: `const qs = require('qs'); const query = qs.stringify({ - publicationFilter: 'has-published-version', + publicationFilter: 'has-published-version', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`);` @@ -602,22 +602,22 @@ await request(\`/api/restaurants?\${query}\`);` status: 200, statusText: "OK", body: `{ - "data": [ - { - "documentId": "a1b2c3d4e5f6g7h8i9j0klm", - "name": "Biscotte Restaurant", - "publishedAt": "2024-03-14T15:40:45.330Z", - "locale": "en" - } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 - } - } + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } }` } ]} @@ -642,10 +642,10 @@ await request(\`/api/restaurants?\${query}\`);` label: "JavaScript", code: `const qs = require('qs'); const query = qs.stringify({ - status: 'draft', - publicationFilter: 'has-published-version-document', + status: 'draft', + publicationFilter: 'has-published-version-document', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`);` @@ -656,22 +656,22 @@ await request(\`/api/restaurants?\${query}\`);` status: 200, statusText: "OK", body: `{ - "data": [ - { - "documentId": "a1b2c3d4e5f6g7h8i9j0klm", - "name": "Biscotte Restaurant", - "publishedAt": null, - "locale": "en" - } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 - } - } + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } }` } ]} From d51aa164fce56d0fbaba0179eeb8f8eba139e197 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:07:26 +0200 Subject: [PATCH 67/75] Apply proper code indentation to the REST status example Indents the qs.stringify object body two more spaces, consistent with the publicationFilter examples. --- docusaurus/docs/cms/api/rest/status.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/status.md b/docusaurus/docs/cms/api/rest/status.md index f1d4ff4484..12af01bd93 100644 --- a/docusaurus/docs/cms/api/rest/status.md +++ b/docusaurus/docs/cms/api/rest/status.md @@ -67,9 +67,9 @@ To select documents by how their draft and published versions relate (never-publ label: 'JavaScript', code: `const qs = require('qs'); const query = qs.stringify({ - status: 'draft', + status: 'draft', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/articles?\${query}\`);`, From 9bd89b68b14d8fbbd3383423838e982d8aaf45fd Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:17:09 +0200 Subject: [PATCH 68/75] Show the full query string in REST publicationFilter Endpoint headers Each Endpoint now carries the complete query (status and publicationFilter) in path and codePath, so the header and the code panel URL bar show the parameters instead of a bare /api/restaurants. The cURL tab holds a real curl command, so no duplicate request line appears. --- .../docs/cms/api/rest/publication-filter.md | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 4472eeae20..9beda9ac9b 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -89,7 +89,8 @@ This parameter combination works only on a given locale; to find these documents Date: Fri, 3 Jul 2026 12:20:44 +0200 Subject: [PATCH 69/75] Show the status query in the REST status Endpoint header The Endpoint header now carries the full query in path and codePath, so it reads /api/articles?status=draft instead of a bare /api/articles. --- docusaurus/docs/cms/api/rest/status.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/api/rest/status.md b/docusaurus/docs/cms/api/rest/status.md index 12af01bd93..ec3e1a7793 100644 --- a/docusaurus/docs/cms/api/rest/status.md +++ b/docusaurus/docs/cms/api/rest/status.md @@ -54,7 +54,8 @@ To select documents by how their draft and published versions relate (never-publ Date: Fri, 3 Jul 2026 12:20:45 +0200 Subject: [PATCH 70/75] Show query strings in REST filters, sort-pagination, and populate-select Endpoint headers Each Endpoint whose example uses parameters now carries the full query in path and codePath, so the header shows the parameters instead of a bare collection path, matching the locale and publicationFilter pages. --- docusaurus/docs/cms/api/rest/filters.md | 12 ++++++++---- docusaurus/docs/cms/api/rest/populate-select.md | 9 ++++++--- docusaurus/docs/cms/api/rest/sort-pagination.md | 12 ++++++++---- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/filters.md b/docusaurus/docs/cms/api/rest/filters.md index bcbf8f2635..7ec5847760 100644 --- a/docusaurus/docs/cms/api/rest/filters.md +++ b/docusaurus/docs/cms/api/rest/filters.md @@ -89,7 +89,8 @@ By default, the filters can only be used from `find` endpoints generated by the Date: Fri, 3 Jul 2026 12:21:51 +0200 Subject: [PATCH 71/75] Add publicationFilter to the List documents query parameters The REST overview page listed status but not publicationFilter in the List documents query-parameters table; adds the row with a link to the dedicated page. --- docusaurus/docs/cms/api/rest.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docusaurus/docs/cms/api/rest.md b/docusaurus/docs/cms/api/rest.md index d3c5fae46d..582d1a9f1f 100644 --- a/docusaurus/docs/cms/api/rest.md +++ b/docusaurus/docs/cms/api/rest.md @@ -151,6 +151,7 @@ You can pass an optional header while you're migrating to Strapi 5 (see the [rel { name: 'pagination[pageSize]', type: 'integer', required: false, description: 'Items per page. Default 25, max 100' }, { name: 'locale', type: 'string', required: false, description: 'Locale of the documents to fetch. See locale.' }, { name: 'status', type: 'string', required: false, description: 'published or draft. See status.' }, + { name: 'publicationFilter', type: 'string', required: false, description: 'Query documents by the relationship between their draft and published versions. See publicationFilter.' }, ]} codePath="/api/restaurants" codePathHighlights={['restaurants']} From 9850f77331849172e5393d6d6a3e7472c93773c3 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:29:43 +0200 Subject: [PATCH 72/75] Apply UX audit fixes to Document Service publicationFilter page Converts the count() example to the Endpoint component with a sample response so every example shares one format, promotes the Content Manager mapping to its own discoverable heading, and adds a contrast sentence to published-with-draft so each published variant self-explains. --- .../document-service/publication-filter.md | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/docusaurus/docs/cms/api/document-service/publication-filter.md b/docusaurus/docs/cms/api/document-service/publication-filter.md index 4773da703a..51a37b1c47 100644 --- a/docusaurus/docs/cms/api/document-service/publication-filter.md +++ b/docusaurus/docs/cms/api/document-service/publication-filter.md @@ -159,10 +159,6 @@ A document counts as published as soon as one of its locales is published: the d ]} /> -:::note Content Manager mapping -In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the per-locale `never-published`). -::: - ### Find modified documents {#modified} `publicationFilter: modified` selects documents whose draft has modified but unpublished changes. `status` then decides which version of those documents you get back. @@ -349,7 +345,7 @@ With `status: 'published'`, the same query returns the currently live version of ### Find published documents with a draft {#published-with-draft} -`publicationFilter: published-with-draft` selects published documents that also have a draft. +`publicationFilter: published-with-draft` selects published documents that also have a draft. Unlike `published-without-draft`, it keeps only the published documents that still have a draft counterpart. `published-with-draft` must be paired with `status: 'published'`: @@ -526,16 +522,36 @@ If the requested document (and locale, when applicable) does not match the filte Without `publicationFilter`, `count({ status: 'draft' })` counts every draft version, including drafts whose document already has a published version. Add `publicationFilter` to count only the documents that match a given value (see the [`status` documentation](/cms/api/document-service/status#count)): -```js -const neverPublishedCount = await strapi - .documents('api::restaurant.restaurant') - .count({ - status: 'draft', - publicationFilter: 'never-published', - }); -``` + ## Combination with other parameters {#combine} `publicationFilter` is combined with other query parameters as a logical `AND`, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same filter logic. +## Content Manager mapping {#content-manager} + +In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the per-locale `never-published`). + From f15b00ad32c67142048bd75f64064886ca3ef096 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:29:43 +0200 Subject: [PATCH 73/75] Apply UX audit fixes to REST publicationFilter page Aligns the frontmatter title with the H1 (REST API: publicationFilter) so search results disambiguate the three API variants, and adds an intro sentence pointing to the Document Service page as the canonical model reference. --- docusaurus/docs/cms/api/rest/publication-filter.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index 9beda9ac9b..c1536b5b49 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -1,5 +1,5 @@ --- -title: Publication filter +title: "REST API: publicationFilter" description: Use the publicationFilter parameter with Strapi's REST API to query documents by the relationship between their draft and published versions, such as never-published or modified documents. sidebarDepth: 3 sidebar_label: Publication filter @@ -28,6 +28,8 @@ The `publicationFilter` is a query parameter that, combined with [the `status` p While `status` answers "do I want the draft or the published version?", the `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". This is useful for example to find documents that were never published, or documents whose draft has unsaved changes compared to what is live. +The [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter) page is the canonical reference for the underlying draft and published model; this page focuses on using the parameter over REST. + :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. ::: @@ -691,5 +693,3 @@ await request(\`/api/restaurants?\${query}\`);` ## Combine with other parameters {#combine} `publicationFilter` can be combined with [`filters`](/cms/api/rest/filters), [`locale`](/cms/api/rest/locale), [`populate`](/cms/api/rest/populate-select), and other [REST parameters](/cms/api/rest/parameters). All conditions are applied together. - -For the full list of values, their exact definitions, and every `status` combination, see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter), which is the reference for the underlying model. From 4290fdf2d4c262bfd140aee109206dbbaae6d5e2 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:30:57 +0200 Subject: [PATCH 74/75] Explain the REST page mirrors the Document Service model at the end of the intro Adds a closing intro paragraph clarifying that the underlying model lives in the Document Service API page and that this page mirrors its structure with REST-tailored examples, so readers do not have to jump between two pages. --- docusaurus/docs/cms/api/rest/publication-filter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/api/rest/publication-filter.md b/docusaurus/docs/cms/api/rest/publication-filter.md index c1536b5b49..afc1ddfdcc 100644 --- a/docusaurus/docs/cms/api/rest/publication-filter.md +++ b/docusaurus/docs/cms/api/rest/publication-filter.md @@ -28,7 +28,7 @@ The `publicationFilter` is a query parameter that, combined with [the `status` p While `status` answers "do I want the draft or the published version?", the `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". This is useful for example to find documents that were never published, or documents whose draft has unsaved changes compared to what is live. -The [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter) page is the canonical reference for the underlying draft and published model; this page focuses on using the parameter over REST. +The underlying model behind how `publicationFilter` works is handled on the back-end server by the [Document Service API](/cms/api/document-service/publication-filter). The present page follows the exact same structure and explanations, but with examples tailored for the REST API, so you don't have to jump between 2 different pages. :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. From 3327977743d7d750a64d5fc01a61885a6153336f Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:31:26 +0200 Subject: [PATCH 75/75] Update LLMs files --- docusaurus/static/llms-code.txt | 938 +++++++++++++++++++++---- docusaurus/static/llms-full.txt | 1150 +++++++++++++++++++++++++------ docusaurus/static/llms.txt | 4 +- 3 files changed, 1762 insertions(+), 330 deletions(-) diff --git a/docusaurus/static/llms-code.txt b/docusaurus/static/llms-code.txt index 00a13bff22..1101e292cc 100644 --- a/docusaurus/static/llms-code.txt +++ b/docusaurus/static/llms-code.txt @@ -4563,116 +4563,382 @@ File path: N/A # Using publicationFilter with the Document Service API Source: https://docs.strapi.io/cms/api/document-service/publication-filter -## Default status when publicationFilter is used -Description: The following example compares Document Service and REST behavior when only publicationFilter: 'modified' is passed: -(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#default-status) +## Find never published drafts +Description: GET strapi.documents().findMany() — findMany() with publicationFilter: +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#never-published) Language: JavaScript File path: N/A -```js -// Document Service API → draft rows in the modified cohort +```javascript await strapi.documents('api::restaurant.restaurant').findMany({ - publicationFilter: 'modified', + status: 'draft', + publicationFilter: 'never-published', }); +``` + +Language: JSON +File path: N/A -// REST: GET /api/restaurants?publicationFilter=modified → published rows in the modified cohort +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "New Restaurant", + publishedAt: null, + locale: "en", // default locale + // … + } + // … +] ``` -## Never-published and modified cohorts -Description: :::note Valid but empty combinations do not return validation errors. -(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#never-published) +## Find drafts never published in any locale +Description: GET strapi.documents().findMany() — findMany() with publicationFilter: +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#never-published-document) Language: JavaScript File path: N/A -```js -// Pair-scoped: drafts never published in this locale +```javascript await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'never-published', + status: 'draft', + publicationFilter: 'never-published-document', }); +``` + +Language: JSON +File path: N/A + +```json +[ + { + documentId: "d41r46wac4xix5vpba7561at", + name: "New Restaurant", + publishedAt: null, + locale: "en", // default locale + // … + } + // … +] +``` + + +## Find modified documents +Description: GET strapi.documents().findMany() — findMany() with publicationFilter: +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#modified) + +Language: JavaScript +File path: N/A -// Modified pairs: draft side vs published side +```javascript await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'modified', + status: 'draft', + publicationFilter: 'modified', }); +``` + +--- +Language: JavaScript +File path: N/A +```javascript await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'published', - publicationFilter: 'modified', + status: 'published', + publicationFilter: 'modified', }); ``` +Language: JSON +File path: N/A + +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant (updated)", + publishedAt: null, + locale: "en", // default locale + // … + } + // … +] +``` + +--- +Language: JSON +File path: N/A + +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: "2024-03-14T15:40:45.330Z", + locale: "en", // default locale + // … + } + // … +] +``` + -## Document-scoped cohorts -Description: :::note Valid but empty combinations do not return validation errors. -(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#document-scoped) +## Find unmodified documents +Description: GET strapi.documents().findMany() — findMany() with publicationFilter: +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#unmodified) Language: JavaScript File path: N/A -```js -// Documents with no published row in any locale +```javascript +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'unmodified', +}); +``` + +--- +Language: JavaScript +File path: N/A + +```javascript await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'never-published-document', + status: 'published', + publicationFilter: 'unmodified', }); ``` +Language: JSON +File path: N/A + +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: null, + locale: "en", // default locale + // … + } + // … +] +``` + +--- +Language: JSON +File path: N/A + +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: "2024-03-14T15:40:45.330Z", + locale: "en", // default locale + // … + } + // … +] +``` + -## Published rows with or without a draft peer -Description: published-without-draft and published-with-draft require status: 'published'. -(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#published-slice) +## Find published documents without a draft +Description: GET strapi.documents().findMany() — findMany() with publicationFilter: +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#published-without-draft) Language: JavaScript File path: N/A -```js +```javascript await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'published', - publicationFilter: 'published-without-draft', + status: 'published', + publicationFilter: 'published-without-draft', +}); +``` + +Language: JSON +File path: N/A + +```json +[ + { + documentId: "j0klm1n2o3p4q5r6s7t8u9v", + name: "Legacy Restaurant", + publishedAt: "2024-01-10T09:15:00.000Z", + locale: "en", // default locale + // … + } + // … +] +``` + + +## Find published documents with a draft +Description: GET strapi.documents().findMany() — findMany() with publicationFilter: +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#published-with-draft) + +Language: JavaScript +File path: N/A + +```javascript +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'published', + publicationFilter: 'published-with-draft', }); +``` + +Language: JSON +File path: N/A + +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: "2024-03-14T15:40:45.330Z", + locale: "en", // default locale + // … + } + // … +] +``` + +## Find documents with a published version +Description: GET strapi.documents().findMany() — findMany() with publicationFilter: +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#has-published-version) + +Language: JavaScript +File path: N/A + +```javascript await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'published', - publicationFilter: 'published-with-draft', + status: 'draft', + publicationFilter: 'has-published-version', +}); +``` + +--- +Language: JavaScript +File path: N/A + +```javascript +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'published', + publicationFilter: 'has-published-version', +}); +``` + +Language: JSON +File path: N/A + +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: null, + locale: "en", // default locale + // … + } + // … +] +``` + +--- +Language: JSON +File path: N/A + +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: "2024-03-14T15:40:45.330Z", + locale: "en", // default locale + // … + } + // … +] +``` + + +## Find documents published in at least one locale +Description: GET strapi.documents().findMany() — findMany() with publicationFilter: +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#has-published-version-document) + +Language: JavaScript +File path: N/A + +```javascript +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'has-published-version-document', }); ``` +Language: JSON +File path: N/A + +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: null, + locale: "en", // published in at least one locale + // … + } + // … +] +``` + -## Use with findOne() and findFirst() -Description: If the requested document (and locale, when applicable) is not in the cohort, findOne() and findFirst() return null even when the documentId exists: -(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#find-one-find-first) +## Use with `findOne()` and `findFirst()` +Description: GET strapi.documents().findOne() — findOne() with publicationFilter: +(Source: https://docs.strapi.io/cms/api/document-service/publication-filter#use-with-findone-and-findfirst) Language: JavaScript File path: N/A -```js +```javascript await strapi.documents('api::restaurant.restaurant').findOne({ - documentId: 'a1b2c3d4e5f6g7h8i9j0klm', - status: 'draft', - publicationFilter: 'never-published', + documentId: 'a1b2c3d4e5f6g7h8i9j0klm', + status: 'draft', + publicationFilter: 'never-published', }); ``` +Language: JSON +File path: N/A + +```json +null // the documentId exists, but the document does not match never-published +``` + -## Count documents in a cohort -Description: Without publicationFilter, count({ status: 'draft' }) still counts every draft row, including drafts whose document already has a published version. +## Count only matching documents +Description: GET strapi.documents().count() — count() with publicationFilter: (Source: https://docs.strapi.io/cms/api/document-service/publication-filter#count) Language: JavaScript File path: N/A -```js +```javascript const neverPublishedCount = await strapi - .documents('api::restaurant.restaurant') - .count({ - status: 'draft', - publicationFilter: 'never-published', - }); + .documents('api::restaurant.restaurant') + .count({ + status: 'draft', + publicationFilter: 'never-published', + }); +``` + +Language: JSON +File path: N/A + +```json +12 // the number of never-published drafts ``` @@ -6170,8 +6436,8 @@ query Query($status: PublicationStatus) { ``` -## Filter by derived publication cohort -Description: When status is omitted, GraphQL defaults to PUBLISHED before applying publicationFilter (same as REST). +## Filter with publicationFilter +Description: :::caution When status is omitted, GraphQL defaults to PUBLISHED before applying publicationFilter (same as REST). (Source: https://docs.strapi.io/cms/api/graphql#publication-filter) Language: GRAPHQL @@ -8438,7 +8704,7 @@ const response = await fetch( Source: https://docs.strapi.io/cms/api/rest/filters ## Example: Find users having 'John' as a first name -Description: GET /api/users — Find users having +Description: GET /api/users?filters[username][$eq]=John — Find users having (Source: https://docs.strapi.io/cms/api/rest/filters#example-find-users-having-john-as-a-first-name) Language: Bash @@ -8498,7 +8764,7 @@ File path: N/A ## Example: Find multiple restaurants with ids 3, 6,8 -Description: GET /api/restaurants — Find multiple restaurants with ids 3, 6, 8 +Description: GET /api/restaurants?filters[id][$in][0]=3&filters[id][$in][1]=6&filters[id][$in][2]=8 — Find multiple restaurants with ids 3, 6, 8 (Source: https://docs.strapi.io/cms/api/rest/filters#example-find-multiple-restaurants-with-ids-3-6-8) Language: Bash @@ -8555,7 +8821,7 @@ File path: N/A ## Complex filtering -Description: GET /api/books — Find books with 2 possible dates and a specific author +Description: GET /api/books?filters[$and][0][$or][0][date][$eq]=2020-01-01&filters[$and][0][$or][1][date][$eq]=2020-01-02&filters[$and][1][author][name][$eq]=Kai%20doe — Find books with 2 possible dates and a specific author (Source: https://docs.strapi.io/cms/api/rest/filters#complex-filtering) Language: Bash @@ -8629,7 +8895,7 @@ File path: N/A ## Deep filtering -Description: GET /api/restaurants — Find restaurants owned by a chef who belongs to a 5-star restaurant +Description: GET /api/restaurants?filters[chef][restaurants][stars][$eq]=5 — Find restaurants owned by a chef who belongs to a 5-star restaurant (Source: https://docs.strapi.io/cms/api/rest/filters#deep-filtering) Language: Bash @@ -10055,7 +10321,7 @@ DELETE /api/homepage?locale=fr Source: https://docs.strapi.io/cms/api/rest/populate-select ## Field selection -Description: GET /api/restaurants — Return only name and description fields +Description: GET /api/restaurants?fields[0]=name&fields[1]=description — Return only name and description fields (Source: https://docs.strapi.io/cms/api/rest/populate-select#field-selection) Language: Bash @@ -10085,7 +10351,7 @@ await request(`/api/users?${query}`); ## Populate with field selection -Description: GET /api/articles — Populate with field selection +Description: GET /api/articles?fields[0]=title&fields[1]=slug&populate[headerImage][fields][0]=name&populate[headerImage][fields][1]=url — Populate with field selection (Source: https://docs.strapi.io/cms/api/rest/populate-select#populate-with-field-selection) Language: Bash @@ -10120,7 +10386,7 @@ await request(`/api/articles?${query}`); ## Populate with filtering -Description: GET /api/articles — Populate with filtering +Description: GET /api/articles?populate[categories][sort][0]=name%3Aasc&populate[categories][filters][name][$eq]=Cars — Populate with filtering (Source: https://docs.strapi.io/cms/api/rest/populate-select#populate-with-filtering) Language: Bash @@ -10159,10 +10425,10 @@ await request(`/api/articles?${query}`); -# Publication filter +# REST API: publicationFilter Source: https://docs.strapi.io/cms/api/rest/publication-filter -## Get never-published draft documents +## Find never published drafts Description: GET /api/restaurants?status=draft&publicationFilter=never-published — Get draft restaurants that have never been published for their locale (Source: https://docs.strapi.io/cms/api/rest/publication-filter#never-published) @@ -10170,7 +10436,8 @@ Language: Bash File path: N/A ```bash -GET /api/restaurants?status=draft&publicationFilter=never-published +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=never-published' \\ + -H 'Authorization: Bearer ' ``` --- @@ -10180,10 +10447,10 @@ File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ - status: 'draft', - publicationFilter: 'never-published', -}, { - encodeValuesOnly: true, // prettify URL + status: 'draft', + publicationFilter: 'never-published', + }, { + encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); @@ -10194,35 +10461,89 @@ File path: N/A ```json { - "data": [ - { - "documentId": "a1b2c3d4e5f6g7h8i9j0klm", - "name": "New Restaurant", - "publishedAt": null, - "locale": "en" - } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "New Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + + +## Find drafts never published in any locale +Description: GET /api/restaurants?status=draft&publicationFilter=never-published-document — Get drafts of restaurants never published in any locale +(Source: https://docs.strapi.io/cms/api/rest/publication-filter#never-published-document) + +Language: Bash +File path: N/A + +```bash +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=never-published-document' \\ + -H 'Authorization: Bearer ' +``` + +--- +Language: JavaScript +File path: N/A + +```js +const qs = require('qs'); +const query = qs.stringify({ + status: 'draft', + publicationFilter: 'never-published-document', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(`/api/restaurants?${query}`); +``` + +Language: JSON +File path: N/A + +```json +{ + "data": [ + { + "documentId": "d41r46wac4xix5vpba7561at", + "name": "New Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } } - } } ``` -## Get modified documents -Description: GET /api/restaurants?publicationFilter=modified — Get published restaurants in the modified cohort (default status) +## Find modified documents +Description: GET /api/restaurants?status=draft&publicationFilter=modified — Get the draft versions of modified restaurants (Source: https://docs.strapi.io/cms/api/rest/publication-filter#modified) Language: Bash File path: N/A ```bash -GET /api/restaurants?publicationFilter=modified +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=modified' \\ + -H 'Authorization: Bearer ' ``` --- @@ -10232,9 +10553,34 @@ File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ - publicationFilter: 'modified', + status: 'draft', + publicationFilter: 'modified', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL +}); + +await request(`/api/restaurants?${query}`); +``` + +--- +Language: Bash +File path: N/A + +```bash +curl 'http://localhost:1337/api/restaurants?publicationFilter=modified' \\ + -H 'Authorization: Bearer ' +``` + +--- +Language: JavaScript +File path: N/A + +```js +const qs = require('qs'); +const query = qs.stringify({ + publicationFilter: 'modified', +}, { + encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); @@ -10245,35 +10591,163 @@ File path: N/A ```json { - "data": [ - { - "documentId": "znrlzntu9ei5onjvwfaalu2v", - "name": "Biscotte Restaurant", - "publishedAt": "2024-03-14T15:40:45.330Z", - "locale": "en" + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant (updated)", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 +} +``` + +--- +Language: JSON +File path: N/A + +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + + +## Find unmodified documents +Description: GET /api/restaurants?status=draft&publicationFilter=unmodified — Get the draft versions of unmodified restaurants +(Source: https://docs.strapi.io/cms/api/rest/publication-filter#unmodified) + +Language: Bash +File path: N/A + +```bash +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=unmodified' \\ + -H 'Authorization: Bearer ' +``` + +--- +Language: JavaScript +File path: N/A + +```js +const qs = require('qs'); +const query = qs.stringify({ + status: 'draft', + publicationFilter: 'unmodified', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(`/api/restaurants?${query}`); +``` + +--- +Language: Bash +File path: N/A + +```bash +curl 'http://localhost:1337/api/restaurants?publicationFilter=unmodified' \\ + -H 'Authorization: Bearer ' +``` + +--- +Language: JavaScript +File path: N/A + +```js +const qs = require('qs'); +const query = qs.stringify({ + publicationFilter: 'unmodified', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(`/api/restaurants?${query}`); +``` + +Language: JSON +File path: N/A + +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + +--- +Language: JSON +File path: N/A + +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } } - } } ``` -## Get published rows without a draft peer -Description: GET /api/restaurants?publicationFilter=published-without-draft — Get published restaurants with no draft row for the same locale +## Find published documents without a draft +Description: GET /api/restaurants?publicationFilter=published-without-draft — Get published restaurants with no draft for the same locale (Source: https://docs.strapi.io/cms/api/rest/publication-filter#published-without-draft) Language: Bash File path: N/A ```bash -GET /api/restaurants?publicationFilter=published-without-draft +curl 'http://localhost:1337/api/restaurants?publicationFilter=published-without-draft' \\ + -H 'Authorization: Bearer ' ``` --- @@ -10283,9 +10757,9 @@ File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ - publicationFilter: 'published-without-draft', + publicationFilter: 'published-without-draft', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); @@ -10296,22 +10770,229 @@ File path: N/A ```json { - "data": [ - { - "documentId": "abcdefghijklmno456", - "name": "Legacy Restaurant", - "publishedAt": "2024-01-10T09:15:00.000Z", - "locale": "en" + "data": [ + { + "documentId": "j0klm1n2o3p4q5r6s7t8u9v", + "name": "Legacy Restaurant", + "publishedAt": "2024-01-10T09:15:00.000Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 +} +``` + + +## Find published documents with a draft +Description: GET /api/restaurants?publicationFilter=published-with-draft — Get published restaurants that also have a draft for the same locale +(Source: https://docs.strapi.io/cms/api/rest/publication-filter#published-with-draft) + +Language: Bash +File path: N/A + +```bash +curl 'http://localhost:1337/api/restaurants?publicationFilter=published-with-draft' \\ + -H 'Authorization: Bearer ' +``` + +--- +Language: JavaScript +File path: N/A + +```js +const qs = require('qs'); +const query = qs.stringify({ + publicationFilter: 'published-with-draft', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(`/api/restaurants?${query}`); +``` + +Language: JSON +File path: N/A + +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + + +## Find documents with a published version +Description: GET /api/restaurants?status=draft&publicationFilter=has-published-version — Get the draft versions of restaurants that also have a published version +(Source: https://docs.strapi.io/cms/api/rest/publication-filter#has-published-version) + +Language: Bash +File path: N/A + +```bash +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=has-published-version' \\ + -H 'Authorization: Bearer ' +``` + +--- +Language: JavaScript +File path: N/A + +```js +const qs = require('qs'); +const query = qs.stringify({ + status: 'draft', + publicationFilter: 'has-published-version', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(`/api/restaurants?${query}`); +``` + +--- +Language: Bash +File path: N/A + +```bash +curl 'http://localhost:1337/api/restaurants?publicationFilter=has-published-version' \\ + -H 'Authorization: Bearer ' +``` + +--- +Language: JavaScript +File path: N/A + +```js +const qs = require('qs'); +const query = qs.stringify({ + publicationFilter: 'has-published-version', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(`/api/restaurants?${query}`); +``` + +Language: JSON +File path: N/A + +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + +--- +Language: JSON +File path: N/A + +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + + +## Find documents published in at least one locale +Description: GET /api/restaurants?status=draft&publicationFilter=has-published-version-document — Get the drafts of restaurants published in at least one locale +(Source: https://docs.strapi.io/cms/api/rest/publication-filter#has-published-version-document) + +Language: Bash +File path: N/A + +```bash +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=has-published-version-document' \\ + -H 'Authorization: Bearer ' +``` + +--- +Language: JavaScript +File path: N/A + +```js +const qs = require('qs'); +const query = qs.stringify({ + status: 'draft', + publicationFilter: 'has-published-version-document', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(`/api/restaurants?${query}`); +``` + +Language: JSON +File path: N/A + +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } } - } } ``` @@ -10623,7 +11304,7 @@ File path: N/A Source: https://docs.strapi.io/cms/api/rest/sort-pagination ## Example: Sort using 2 fields -Description: GET /api/restaurants — Sort using 2 fields +Description: GET /api/restaurants?sort[0]=Description&sort[1]=Name — Sort using 2 fields (Source: https://docs.strapi.io/cms/api/rest/sort-pagination#example-sort-using-2-fields) Language: Bash @@ -10698,7 +11379,7 @@ File path: N/A ## Example: Sort using 2 fields and set the order -Description: GET /api/restaurants — Sort using 2 fields and set the order +Description: GET /api/restaurants?sort[0]=Description:asc&sort[1]=Name:desc — Sort using 2 fields and set the order (Source: https://docs.strapi.io/cms/api/rest/sort-pagination#example-sort-using-2-fields-and-set-the-order) Language: Bash @@ -10773,7 +11454,7 @@ File path: N/A ## Pagination by page -Description: GET /api/articles — Pagination by page +Description: GET /api/articles?pagination[page]=1&pagination[pageSize]=10 — Pagination by page (Source: https://docs.strapi.io/cms/api/rest/sort-pagination#pagination-by-page) Language: Bash @@ -10822,7 +11503,7 @@ File path: N/A ## Pagination by offset -Description: GET /api/articles — Pagination by offset +Description: GET /api/articles?pagination[start]=0&pagination[limit]=10 — Pagination by offset (Source: https://docs.strapi.io/cms/api/rest/sort-pagination#pagination-by-offset) Language: Bash @@ -10877,15 +11558,24 @@ Source: https://docs.strapi.io/cms/api/rest/status Description: GET /api/articles?status=draft — Get draft versions of restaurants (Source: https://docs.strapi.io/cms/api/rest/status#status) +Language: Bash +File path: N/A + +```bash +curl 'http://localhost:1337/api/articles?status=draft' \\ + -H 'Authorization: Bearer ' +``` + +--- Language: JavaScript File path: N/A ```js const qs = require('qs'); const query = qs.stringify({ - status: 'draft', + status: 'draft', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(`/api/articles?${query}`); diff --git a/docusaurus/static/llms-full.txt b/docusaurus/static/llms-full.txt index 6a732e178e..71a9e4b77c 100644 --- a/docusaurus/static/llms-full.txt +++ b/docusaurus/static/llms-full.txt @@ -4675,7 +4675,7 @@ Find a document matching the passed documentId and parameters. If only a documen - `documentId` (ID, required): Document id - `locale` (String or undefined): Locale of the document to find. Defaults to the default locale. See locale docs (/cms/api/document-service/locale#find-one). - `status` (): If Draft & Publish (/cms/features/draft-and-publish) is enabled: publication status. Can be `published` or `draft`. Default: `draft`. See status docs (/cms/api/document-service/status#find-one). -- `publicationFilter` (String): If Draft & Publish (/cms/features/draft-and-publish) is enabled: derived publication cohort to match before applying `status`. See publicationFilter docs (/cms/api/document-service/publication-filter). +- `publicationFilter` (String): If Draft & Publish (/cms/features/draft-and-publish) is enabled: select documents by how their draft and published versions relate, before applying `status`. See publicationFilter docs (/cms/api/document-service/publication-filter). - `fields` (Object): Select fields (/cms/api/document-service/fields#findone) to return. Defaults to all fields (except those not populated by default). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. @@ -4707,7 +4707,7 @@ Find the first document matching the parameters. By default, findFirst() returns **Parameters:** - `locale` (String or undefined): Locale of the documents to find. Defaults to the default locale. See locale docs (/cms/api/document-service/locale#find-first). - `status` (): If Draft & Publish (/cms/features/draft-and-publish) is enabled: publication status. Can be `published` or `draft`. Default: `draft`. See status docs (/cms/api/document-service/status#find-first). -- `publicationFilter` (String): If Draft & Publish (/cms/features/draft-and-publish) is enabled: derived publication cohort to match before applying `status`. See publicationFilter docs (/cms/api/document-service/publication-filter). +- `publicationFilter` (String): If Draft & Publish (/cms/features/draft-and-publish) is enabled: select documents by how their draft and published versions relate, before applying `status`. See publicationFilter docs (/cms/api/document-service/publication-filter). - `filters` (Object): Filters (/cms/api/document-service/filters) to use. Default: `null`. - `fields` (Object): Select fields (/cms/api/document-service/fields#findfirst) to return. Defaults to all fields (except those not populated by default). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. @@ -4763,7 +4763,7 @@ Find documents matching the parameters. When no parameter is passed, findMany() **Parameters:** - `locale` (String or undefined): Locale of the documents to find. Defaults to the default locale. See locale docs (/cms/api/document-service/locale#find-many). - `status` (): If Draft & Publish (/cms/features/draft-and-publish) is enabled: publication status. Can be `published` or `draft`. Default: `draft`. See status docs (/cms/api/document-service/status#find-many). -- `publicationFilter` (String): If Draft & Publish (/cms/features/draft-and-publish) is enabled: derived publication cohort to match before applying `status`. See publicationFilter docs (/cms/api/document-service/publication-filter). +- `publicationFilter` (String): If Draft & Publish (/cms/features/draft-and-publish) is enabled: select documents by how their draft and published versions relate, before applying `status`. See publicationFilter docs (/cms/api/document-service/publication-filter). - `filters` (Object): Filters (/cms/api/document-service/filters) to use. Default: `null`. - `fields` (Object): Select fields (/cms/api/document-service/fields#findmany) to return. Defaults to all fields (except those not populated by default). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. @@ -5099,7 +5099,7 @@ Count how many documents match the parameters. If no parameter is passed, the co **Parameters:** - `locale` (String or null): Locale of the documents to count. Defaults to the default locale. See locale docs (/cms/api/document-service/locale#count). - `status` (): If Draft & Publish (/cms/features/draft-and-publish) is enabled: publication status. `published` to count only published documents, `draft` to count draft documents (returns all documents). Default: `draft`. See status docs (/cms/api/document-service/status#count). -- `publicationFilter` (String): If Draft & Publish (/cms/features/draft-and-publish) is enabled: derived publication cohort to match before applying `status`. See publicationFilter docs (/cms/api/document-service/publication-filter). +- `publicationFilter` (String): If Draft & Publish (/cms/features/draft-and-publish) is enabled: select documents by how their draft and published versions relate, before applying `status`. See publicationFilter docs (/cms/api/document-service/publication-filter). - `filters` (Object): Filters (/cms/api/document-service/filters) to use. Default: `null`. **Generic example:** @@ -7059,170 +7059,461 @@ Source: https://docs.strapi.io/cms/api/document-service/publication-filter # Document Service API: `publicationFilter` -The [`status`](/cms/api/document-service/status) parameter selects which **row slice** to read for each document: `draft` rows have `publishedAt: null`, and `published` rows have a non-null `publishedAt`. +Use the optional `publicationFilter` parameter to query documents by the relationship between their draft and published versions, for example drafts that were never published, or entries modified since they were last published. It works with `findOne()`, `findFirst()`, `findMany()`, and `count()`, and combines with other query parameters. `status` still decides whether you get the draft or the published version. -The optional `publicationFilter` parameter selects a **derived publication cohort** first: a set of `(documentId, locale)` pairs (or `documentId` only when [Internationalization (i18n)](/cms/features/internationalization) is disabled) defined by how draft and published rows relate. Cohorts compare draft and published rows for the same document; a single row's `publishedAt` is not enough. Strapi then returns the row that matches both the cohort and the resolved `status`. +The `publicationFilter` is a parameter that, combined with [the `status` parameter](/cms/api/document-service/status), can help you cover complex queries to find exactly what you need with the [Document Service API](/cms/api/document-service). + +While `status` answers "do I want the draft or the published version?", the `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". This is useful for example to find drafts that were never published, or entries whose draft has unsaved changes compared to what is live. :::prerequisites The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. ::: -`publicationFilter` is supported on `findOne()`, `findFirst()`, `findMany()`, and `count()`. It is combined with other query parameters as a logical AND, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same cohort logic. Unknown values raise a validation error (REST returns HTTP `400`; GraphQL fails at query validation). +## Available values {#values} + +`publicationFilter` accepts one of the following values: + +| Value | Selects | +| ----- | ------- | +| `never-published` | Documents never published in a given locale | +| `never-published-document` | Documents never published in any locale | +| `modified` | Documents whose draft was edited since it was last published | +| `unmodified` | Documents whose draft has not changed since it was last published | +| `published-without-draft` | Published documents with no draft counterpart | +| `published-with-draft` | Published documents that also have a draft | +| `has-published-version` | Documents that have both a draft and a published version | +| `has-published-version-document` | Documents published in at least one locale
(useful when [i18n](/cms/features/internationalization) is enabled) | -In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not pair-scoped `never-published`). Other Status filter options use internal APIs, not public `publicationFilter` parameters. +For detailed examples of how to use the `publicationFilter` values, including with the `status` parameter, see the [possible use cases](#use-cases) table. -## Default `status` when `publicationFilter` is used {#default-status} +:::note +* Unknown values raise a validation error. +* Values ending in `-document` consider all locales of a document, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: for example, `never-published-document` excludes a document as soon as one of its locales is published. All other values consider one locale at a time. Without i18n, both variants behave the same. +::: -`publicationFilter` is applied **after** `status` is resolved (explicitly or by default). Defaults differ by API surface: +:::caution Caution: Different default behaviors for different APIs +The Document Service API returns draft versions of documents when `status` is omitted, while REST and GraphQL return the published ones instead, so REST API queries need an explicit `status` (see [REST API: `publicationFilter`](/cms/api/rest/publication-filter)). +::: -| API surface | Default `status` when omitted | -| ----------- | ----------------------------- | -| Document Service API (direct) | `'draft'` | -| [REST API](/cms/api/rest/publication-filter) | `'published'` | -| [GraphQL API](/cms/api/graphql#publication-filter) | `PUBLISHED` | +## Possible use cases {#use-cases} -The following example compares Document Service and REST behavior when only `publicationFilter: 'modified'` is passed: +The following table lists many possible use cases, illustrating how the `status` and `publicationFilter` parameters can be combined to find exactly what you need with the Document Service API. Click a use case to jump to a complete example: -```js -// Document Service API → draft rows in the modified cohort +| I want to… | Use `status` as… | Use `publicationFilter` as… | +| ---------- | ------------------ | ----------------------------- | +| [Find never published drafts](#never-published) | `draft` | `never-published` | +| [Find drafts never published in any locale](#never-published-document) | `draft` | `never-published-document` | +| [Find modified documents](#modified) | `draft` or `published` | `modified` | +| [Find unmodified documents](#unmodified) | `draft` or `published` | `unmodified` | +| [Find published documents without a draft](#published-without-draft) | `published` | `published-without-draft` | +| [Find published documents with a draft](#published-with-draft) | `published` | `published-with-draft` | +| [Find documents with a published version](#has-published-version) | `draft` or `published` | `has-published-version` | +| [Find documents published in at least one locale](#has-published-version-document) | `draft` or `published` | `has-published-version-document` | +| [Use with `findOne()` and `findFirst()`](#find-one-find-first) | `draft` or `published` | any value | +| [Count only matching documents](#count) | `draft` or `published` | any value | + +:::note +Pairing a value with the opposite `status` from the table above is valid but returns nothing rather than an error: for example, `never-published` with `status: 'published'` returns an empty result, because these documents have no published version yet. +::: + +## Examples + +The following section lists the most common use cases summed up in the [table](#use-cases) above. + +### Find never published drafts {#never-published} + +One of the most common use cases is to find the drafts that have never been published. To do so, pass `status: 'draft'` and `publicationFilter: 'never-published'`. + +This parameter combination works only on a given locale; to find these documents across all locales, [use `never-published-document`](#never-published-document) instead. + +#### GET strapi.documents().findMany() — findMany() with publicationFilter: + +Return the drafts that have never been published for their locale. + +**JavaScript:** +``` await strapi.documents('api::restaurant.restaurant').findMany({ - publicationFilter: 'modified', + status: 'draft', + publicationFilter: 'never-published', }); +``` -// REST: GET /api/restaurants?publicationFilter=modified → published rows in the modified cohort +**Response 200 OK:** +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "New Restaurant", + publishedAt: null, + locale: "en", // default locale + // … + } + // … +] ``` -Pair-scoped modes such as `never-published` only include draft rows in the cohort. With REST or GraphQL defaults (`status=published`), those queries return an empty result set unless you pass `status=draft` / `status: DRAFT`. +### Find drafts never published in any locale {#never-published-document} -## Available values {#values} +`publicationFilter: never-published-document` returns documents that have never been published in any locale. It looks at the whole document across all its locales, not one locale at a time. To find these documents for a given locale only, [use `never-published`](#never-published) instead. -REST and the Document Service API use kebab-case strings. GraphQL exposes the same cohorts through the [`PublicationFilter` enum](/cms/api/graphql#publication-filter). +A document counts as published as soon as one of its locales is published: the document is then left out, even the locales that only exist as a draft. The example below returns the draft versions of documents that were never published anywhere: -| Value | Scope | Cohort definition (which `(documentId, locale)` pairs match) | -| ----- | ----- | -------------------------------------------------------------- | -| `never-published` | Pair | No row with non-null `publishedAt` exists for the same `(documentId, locale)` | -| `has-published-version` | Pair | **Both** a draft row and a published row exist for the same `(documentId, locale)` | -| `modified` | Pair | Both slices exist and `draft.updatedAt > published.updatedAt` | -| `unmodified` | Pair | Both slices exist and `draft.updatedAt <= published.updatedAt` | -| `never-published-document` | Document | No row with non-null `publishedAt` exists for the same `documentId` in **any** locale | -| `has-published-version-document` | Document | At least one published row exists for the same `documentId` in **any** locale | -| `published-without-draft` | Pair | A published row exists for the pair and **no** draft row exists for the same `(documentId, locale)` | -| `published-with-draft` | Pair | A published row exists for the pair and a draft row **also** exists for the same `(documentId, locale)` | +#### GET strapi.documents().findMany() — findMany() with publicationFilter: -For content-types without i18n, read `(documentId, locale)` as `documentId` only. +Return the drafts of documents never published in any locale. -:::note -`has-published-version` excludes orphan published rows (published-only pairs with no draft sibling). Those pairs match `published-without-draft` when `status` is `'published'`. -::: +**JavaScript:** +``` +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'never-published-document', +}); +``` + +**Response 200 OK:** +```json +[ + { + documentId: "d41r46wac4xix5vpba7561at", + name: "New Restaurant", + publishedAt: null, + locale: "en", // default locale + // … + } + // … +] +``` -## Combine `status` and `publicationFilter` {#status-combination} +### Find modified documents {#modified} -Pass `status` explicitly or rely on the [default for your API surface](#default-status). Each table lists which rows a `publicationFilter` returns for that `status`. +`publicationFilter: modified` selects documents whose draft has modified but unpublished changes. `status` then decides which version of those documents you get back. -### With `status: 'draft'` +For instance, with `status: 'draft'`, the query returns the draft versions: -| `publicationFilter` | Rows returned | -| ------------------- | ------------- | -| `never-published` | Draft rows for pairs never published in that locale | -| `has-published-version` | Draft rows for pairs that also have a published version | -| `modified` | Draft rows newer than their published peer | -| `unmodified` | Draft rows not newer than their published peer | -| `never-published-document` | Draft rows whose document has no published row in any locale | -| `has-published-version-document` | Draft rows whose document has at least one published row (any locale) | -| `published-without-draft`, `published-with-draft` | No rows | +#### GET strapi.documents().findMany() — findMany() with publicationFilter: -### With `status: 'published'` +Return the draft versions of documents with unpublished changes. -| `publicationFilter` | Rows returned | -| ------------------- | ------------- | -| `has-published-version` | Published rows for pairs that also have a draft version (excludes orphan published-only pairs) | -| `modified` | Published rows whose draft peer is newer | -| `unmodified` | Published rows whose draft peer is not newer | -| `has-published-version-document` | Published rows whose document has at least one draft row (any locale) | -| `published-without-draft` | Published rows with no draft sibling for the same pair | -| `published-with-draft` | Published rows that have a draft sibling for the same pair | -| `never-published`, `never-published-document` | No rows | +**JavaScript:** +``` +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'modified', +}); +``` -:::note -Valid but empty combinations do not return validation errors. -::: +**Response 200 OK:** +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant (updated)", + publishedAt: null, + locale: "en", // default locale + // … + } + // … +] +``` + +
+With `status: 'published'`, the same query returns the currently live version of those documents instead: -## Examples {#examples} +#### GET strapi.documents().findMany() — findMany() with publicationFilter: -### Never-published and modified cohorts {#never-published} +Return the currently live versions of documents with unpublished changes. -```js -// Pair-scoped: drafts never published in this locale +**JavaScript:** +``` await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'never-published', + status: 'published', + publicationFilter: 'modified', }); +``` + +**Response 200 OK:** +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: "2024-03-14T15:40:45.330Z", + locale: "en", // default locale + // … + } + // … +] +``` + +### Find unmodified documents {#unmodified} -// Modified pairs: draft side vs published side +`publicationFilter: unmodified` selects documents whose draft has not changed since it was last published. `status` then decides which version of those documents you get back. + +For instance, with `status: 'draft'`, the query returns the draft versions: + +#### GET strapi.documents().findMany() — findMany() with publicationFilter: + +Return the draft versions of documents unchanged since their last publication. + +**JavaScript:** +``` await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'modified', + status: 'draft', + publicationFilter: 'unmodified', }); +``` +**Response 200 OK:** +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: null, + locale: "en", // default locale + // … + } + // … +] +``` + +
+With `status: 'published'`, the same query returns the currently live version of those documents instead: + +#### GET strapi.documents().findMany() — findMany() with publicationFilter: + +Return the currently live versions of documents unchanged since their last publication. + +**JavaScript:** +``` await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'published', - publicationFilter: 'modified', + status: 'published', + publicationFilter: 'unmodified', }); ``` -### Document-scoped cohorts {#document-scoped} +**Response 200 OK:** +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: "2024-03-14T15:40:45.330Z", + locale: "en", // default locale + // … + } + // … +] +``` + +### Find published documents without a draft {#published-without-draft} -```js -// Documents with no published row in any locale +`publicationFilter: published-without-draft` selects published documents that have no draft counterpart. + +`published-without-draft` must be paired with `status: 'published'`: + +#### GET strapi.documents().findMany() — findMany() with publicationFilter: + +Return published documents with no matching draft version for the same locale. + +**JavaScript:** +``` await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'draft', - publicationFilter: 'never-published-document', + status: 'published', + publicationFilter: 'published-without-draft', }); ``` -A multi-locale document with one published locale is excluded entirely, including its draft-only locales. +**Response 200 OK:** +```json +[ + { + documentId: "j0klm1n2o3p4q5r6s7t8u9v", + name: "Legacy Restaurant", + publishedAt: "2024-01-10T09:15:00.000Z", + locale: "en", // default locale + // … + } + // … +] +``` + +### Find published documents with a draft {#published-with-draft} -### Published rows with or without a draft peer {#published-slice} +`publicationFilter: published-with-draft` selects published documents that also have a draft. Unlike `published-without-draft`, it keeps only the published documents that still have a draft counterpart. -`published-without-draft` and `published-with-draft` require `status: 'published'`. +`published-with-draft` must be paired with `status: 'published'`: -```js +#### GET strapi.documents().findMany() — findMany() with publicationFilter: + +Return published documents that also have a matching draft version for the same locale. + +**JavaScript:** +``` await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'published', - publicationFilter: 'published-without-draft', + status: 'published', + publicationFilter: 'published-with-draft', }); +``` + +**Response 200 OK:** +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: "2024-03-14T15:40:45.330Z", + locale: "en", // default locale + // … + } + // … +] +``` + +### Find documents with a published version {#has-published-version} +`publicationFilter: has-published-version` selects documents that have both a draft and a published version for the same locale. `status` then decides which version of those documents you get back. Unlike `published-without-draft`, it excludes published documents that have no draft counterpart. + +For instance, with `status: 'draft'`, the query returns the draft versions: + +#### GET strapi.documents().findMany() — findMany() with publicationFilter: + +Return the draft versions of documents that also have a published version for the same locale. + +**JavaScript:** +``` await strapi.documents('api::restaurant.restaurant').findMany({ - status: 'published', - publicationFilter: 'published-with-draft', + status: 'draft', + publicationFilter: 'has-published-version', }); ``` -## Use with `findOne()` and `findFirst()` {#find-one-find-first} +**Response 200 OK:** +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: null, + locale: "en", // default locale + // … + } + // … +] +``` + +
+With `status: 'published'`, the same query returns the currently live version of those documents instead: + +#### GET strapi.documents().findMany() — findMany() with publicationFilter: -If the requested document (and locale, when applicable) is not in the cohort, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: +Return the currently live versions of documents that also have a published version for the same locale. -```js -await strapi.documents('api::restaurant.restaurant').findOne({ - documentId: 'a1b2c3d4e5f6g7h8i9j0klm', - status: 'draft', - publicationFilter: 'never-published', +**JavaScript:** +``` +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'published', + publicationFilter: 'has-published-version', +}); +``` + +**Response 200 OK:** +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: "2024-03-14T15:40:45.330Z", + locale: "en", // default locale + // … + } + // … +] +``` + +### Find documents published in at least one locale {#has-published-version-document} + +`publicationFilter: has-published-version-document` considers all locales, so it matches a document as soon as one of its locales is published. With `status: 'draft'`, it returns the draft versions of every locale of those documents, including locales that were never published themselves: + +#### GET strapi.documents().findMany() — findMany() with publicationFilter: + +Return the draft versions of documents published in at least one locale. + +**JavaScript:** +``` +await strapi.documents('api::restaurant.restaurant').findMany({ + status: 'draft', + publicationFilter: 'has-published-version-document', }); ``` -## Count documents in a cohort {#count} +**Response 200 OK:** +```json +[ + { + documentId: "a1b2c3d4e5f6g7h8i9j0klm", + name: "Biscotte Restaurant", + publishedAt: null, + locale: "en", // published in at least one locale + // … + } + // … +] +``` -Without `publicationFilter`, `count({ status: 'draft' })` still counts every draft row, including drafts whose document already has a published version. Use `publicationFilter: 'never-published'` or `'never-published-document'` to count only never-published cohorts (see [`status` documentation](/cms/api/document-service/status#count)). +### Use with `findOne()` and `findFirst()` {#find-one-find-first} -```js -const neverPublishedCount = await strapi - .documents('api::restaurant.restaurant') - .count({ +If the requested document (and locale, when applicable) does not match the filter, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: + +#### GET strapi.documents().findOne() — findOne() with publicationFilter: + +Return the document only if it matches the filter, null otherwise. + +**JavaScript:** +``` +await strapi.documents('api::restaurant.restaurant').findOne({ + documentId: 'a1b2c3d4e5f6g7h8i9j0klm', status: 'draft', publicationFilter: 'never-published', - }); +}); +``` + +**Response 200 OK:** +```json +null // the documentId exists, but the document does not match never-published +``` + +### Count only matching documents {#count} + +Without `publicationFilter`, `count({ status: 'draft' })` counts every draft version, including drafts whose document already has a published version. Add `publicationFilter` to count only the documents that match a given value (see the [`status` documentation](/cms/api/document-service/status#count)): + +#### GET strapi.documents().count() — count() with publicationFilter: + +Count only the documents that match a given value. + +**JavaScript:** +``` +const neverPublishedCount = await strapi + .documents('api::restaurant.restaurant') + .count({ + status: 'draft', + publicationFilter: 'never-published', + }); +``` + +**Response 200 OK:** +```json +12 // the number of never-published drafts ``` +## Combination with other parameters {#combine} + +`publicationFilter` is combined with other query parameters as a logical `AND`, including [`filters`](/cms/api/document-service/filters) and [`populate`](/cms/api/document-service/populate). When populating draft & publish relations, nested queries inherit the same filter logic. + +## Content Manager mapping {#content-manager} + +In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the per-locale `never-published`). + # Using Sort & Pagination with the Document Service API @@ -7351,7 +7642,7 @@ By default the [Document Service API](/cms/api/document-service) returns the dra Passing `{ status: 'draft' }` to a Document Service API query returns the same results as not passing any `status` parameter. ::: -For derived publication cohorts (never-published, modified, and others), see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). +To select documents by how their draft and published versions relate (never-published, modified, and others), see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). ## Get the published version with `findOne()` {#find-one} @@ -7450,7 +7741,7 @@ const publishedCount = await strapi.documents("api::restaurant.restaurant").coun :::note Since published documents necessarily also have a draft counterpart, a published document is still counted as having a draft version. -This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. To count only never-published drafts, pass a [`publicationFilter`](/cms/api/document-service/publication-filter) value such as `'never-published'` or `'never-published-document'`. +This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. To count only never-published drafts, [pass a `publicationFilter` value](/cms/api/document-service/publication-filter) such as `'never-published'` or `'never-published-document'`. ::: ## Create a draft and publish it {#create} @@ -8823,13 +9114,15 @@ query Query($status: PublicationStatus) { } ``` -### Filter by derived publication cohort {#publication-filter} +### Filter with `publicationFilter` {#publication-filter} -If the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled, you can add a `publicationFilter` argument to built-in collection and single-type queries. The GraphQL plugin exposes the same cohorts as the REST API and Document Service API through the `PublicationFilter` enum. +If the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled, you can add a `publicationFilter` argument to built-in collection and single-type queries. It filters documents by the [relationship between their draft and published versions](/cms/api/document-service/publication-filter): for example, drafts that were never published, or entries modified since they were last published. GraphQL exposes the same values as the REST API and the Document Service API through the `PublicationFilter` enum. -Combine `publicationFilter` with `status` the same way as for REST (see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter#status-combination)). +`publicationFilter` selects the group of documents first; the `status` argument then decides whether each result returns its draft or published row. -When `status` is omitted, GraphQL defaults to `PUBLISHED` before applying `publicationFilter` (same as REST). Example: `restaurants(publicationFilter: MODIFIED)` returns published rows in the modified cohort; use `status: DRAFT` to return draft rows instead. +:::caution +When `status` is omitted, GraphQL defaults to `PUBLISHED` before applying `publicationFilter` (same as REST). Draft-only values such as `NEVER_PUBLISHED` return no results unless you pass `status: DRAFT`. +::: ```graphql title="Example: Fetch never-published draft documents" query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { @@ -8841,7 +9134,7 @@ query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { } ``` -```graphql title="Example: Modified cohort with default PUBLISHED status" +```graphql title="Example: Modified documents with default PUBLISHED status" query Query { restaurants(publicationFilter: MODIFIED) { documentId @@ -8864,6 +9157,8 @@ Available enum values: | `PUBLISHED_WITHOUT_DRAFT` | `published-without-draft` | | `PUBLISHED_WITH_DRAFT` | `published-with-draft` | +To learn more, see the [use cases and accepted values](/cms/api/document-service/publication-filter#values) on the Document Service API page. + ## Mutations Mutations in GraphQL are used to modify data (e.g. create, update, and delete data). @@ -11189,6 +11484,7 @@ Returns a paginated list of documents. Supports filtering, sorting, field select - `pagination[pageSize]` (integer): Items per page. Default `25`, max `100` - `locale` (string): Locale of the documents to fetch. See locale (/cms/api/rest/locale). - `status` (string): `published` or `draft`. See status (/cms/api/rest/status). +- `publicationFilter` (string): Query documents by the relationship between their draft and published versions. See publicationFilter (/cms/api/rest/publication-filter). **cURL:** ``` @@ -11606,7 +11902,7 @@ By default, the filters can only be used from `find` endpoints generated by the ## Example: Find users having 'John' as a first name -#### GET /api/users — Find users having +#### GET /api/users?filters[username][$eq]=John — Find users having Use the $eq filter operator to find an exact match. @@ -11660,7 +11956,7 @@ await request(\`/api/users?\${query}\`); ## Example: Find multiple restaurants with ids 3, 6,8 -#### GET /api/restaurants — Find multiple restaurants with ids 3, 6, 8 +#### GET /api/restaurants?filters[id][$in][0]=3&filters[id][$in][1]=6&filters[id][$in][2]=8 — Find multiple restaurants with ids 3, 6, 8 Use the $in filter operator with an array of values to find multiple exact values. @@ -11711,7 +12007,7 @@ await request(\`/api/restaurants?\${query}\`); ## Complex filtering -#### GET /api/books — Find books with 2 possible dates and a specific author +#### GET /api/books?filters[$and][0][$or][0][date][$eq]=2020-01-01&filters[$and][0][$or][1][date][$eq]=2020-01-02&filters[$and][1][author][name][$eq]=Kai%20doe — Find books with 2 possible dates and a specific author Combine $and and $or operators for complex filtering. @@ -11796,7 +12092,7 @@ Querying your API with deep filters may cause performance issues. If one of you For examples of how to deep filter with the various APIs, please refer to [this blog article](https://strapi.io/blog/deep-filtering-alpha-26). ::: -#### GET /api/restaurants — Find restaurants owned by a chef who belongs to a 5-star restaurant +#### GET /api/restaurants?filters[chef][restaurants][stars][$eq]=5 — Find restaurants owned by a chef who belongs to a 5-star restaurant Use deep filtering to filter on a relation @@ -13782,7 +14078,7 @@ The following API parameters are available: | `filters` | Object | [Filter the response](/cms/api/rest/filters) | | `locale` | String | [Select a locale](/cms/api/rest/locale) | | `status` | String | [Select the Draft & Publish status](/cms/api/rest/status) | -| `publicationFilter` | String | [Select a derived Draft & Publish cohort](/cms/api/rest/publication-filter) | +| `publicationFilter` | String | [Select documents by how their draft and published versions relate](/cms/api/rest/publication-filter) | | `populate` | String or Object | [Populate relations, components, or dynamic zones](/cms/api/rest/populate-select#population) | | `fields` | Array | [Select only specific fields to display](/cms/api/rest/populate-select#field-selection) | | `sort` | String or Array | [Sort the response](/cms/api/rest/sort-pagination.md#sorting) | @@ -13837,7 +14133,7 @@ Queries can accept a `fields` parameter to select only some fields. By default, Field selection does not work on relational, media, component, or dynamic zone fields. To populate these fields, use the [`populate` parameter](#population). ::: -#### GET /api/restaurants — Return only name and description fields +#### GET /api/restaurants?fields[0]=name&fields[1]=description — Return only name and description fields Use the fields parameter to select only specific fields in the response. @@ -13940,7 +14236,7 @@ Top-level pagination parameters (e.g., `pagination[page]` and `pagination[pageSi `fields` and `populate` can be combined. -#### GET /api/articles — Populate with field selection +#### GET /api/articles?fields[0]=title&fields[1]=slug&populate[headerImage][fields][0]=name&populate[headerImage][fields][1]=url — Populate with field selection Combine fields and populate parameters to select specific fields on both the main entry and its relations. @@ -13994,7 +14290,7 @@ await request(\`/api/articles?\${query}\`); `filters` and `populate` can be combined. -#### GET /api/articles — Populate with filtering +#### GET /api/articles?populate[categories][sort][0]=name%3Aasc&populate[categories][filters][name][$eq]=Cars — Populate with filtering Combine populate with sort and filter parameters to refine which related entries are returned. @@ -14060,56 +14356,147 @@ In production, always use explicit population instead of wildcards like `populat -# Publication filter +# REST API: publicationFilter Source: https://docs.strapi.io/cms/api/rest/publication-filter # REST API: `publicationFilter` -The [REST API](/cms/api/rest) accepts an optional `publicationFilter` query parameter when [Draft & Publish](/cms/features/draft-and-publish) is enabled. Use it to query derived publication cohorts such as never-published or modified documents. The [`status`](/cms/api/rest/status) parameter still selects whether each matching document returns its draft or published row. +Add the optional `publicationFilter` query parameter to query documents by the relationship between their draft and published versions, for example documents that were never published, or documents modified since they were last published. It combines with other query parameters, and `status` still decides whether you get the draft or the published version. + +The `publicationFilter` is a query parameter that, combined with [the `status` parameter](/cms/api/rest/status), can help you cover complex queries to find exactly what you need with the [REST API](/cms/api/rest). + +While `status` answers "do I want the draft or the published version?", the `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". This is useful for example to find documents that were never published, or documents whose draft has unsaved changes compared to what is live. + +The underlying model behind how `publicationFilter` works is handled on the back-end server by the [Document Service API](/cms/api/document-service/publication-filter). The present page follows the exact same structure and explanations, but with examples tailored for the REST API, so you don't have to jump between 2 different pages. :::prerequisites -The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. +The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. +::: + +## Available values {#values} + +`publicationFilter` accepts one of the following values: + +| Value | Selects | +| ----- | ------- | +| `never-published` | Documents never published in a given locale | +| `never-published-document` | Documents never published in any locale | +| `modified` | Documents whose draft was edited since it was last published | +| `unmodified` | Documents whose draft has not changed since it was last published | +| `published-without-draft` | Published documents with no draft counterpart | +| `published-with-draft` | Published documents that also have a draft | +| `has-published-version` | Documents that have both a draft and a published version | +| `has-published-version-document` | Documents published in at least one locale
(useful when [i18n](/cms/features/internationalization) is enabled) | + +For detailed examples of how to use the `publicationFilter` values, including with the `status` parameter, see the [possible use cases](#use-cases) table. + +:::note +* Unknown values return an HTTP `400` error. +* Values ending in `-document` consider all locales of a document, which matters when [Internationalization (i18n)](/cms/features/internationalization) is enabled: for example, `never-published-document` excludes a document as soon as one of its locales is published. All other values consider one locale at a time. Without i18n, both variants behave the same. ::: -## Default `status` {#default-status} +:::caution Caution: Different default behaviors for different APIs +The REST API returns published versions of documents when `status` is omitted, so queries for draft-only values such as `never-published` need an explicit `status=draft`. The Document Service API returns draft versions instead (see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter)). +::: -When `status` is omitted, the REST API defaults to `status=published` **before** applying `publicationFilter`. +## Possible use cases {#use-cases} -| Query | Effective behavior | -| ----- | ------------------ | -| `?publicationFilter=never-published` | Empty (cohort is draft-only; default status is `published`) | -| `?status=draft&publicationFilter=never-published` | Never-published draft rows | -| `?publicationFilter=modified` | Published rows in the modified cohort | -| `?status=draft&publicationFilter=modified` | Draft rows in the modified cohort | -| `?publicationFilter=published-without-draft` | Orphan published rows (default `status=published` is correct) | +The following table lists many possible use cases, illustrating how the `status` and `publicationFilter` parameters can be combined to find exactly what you need with the REST API. Click a use case to jump to a complete example: -The Document Service API defaults to `status=draft` instead. See [Document Service API: default `status`](/cms/api/document-service/publication-filter#default-status). +| I want to… | Use `status` as… | Use `publicationFilter` as… | +| ---------- | ------------------ | ----------------------------- | +| [Find never published drafts](#never-published) | `draft` | `never-published` | +| [Find drafts never published in any locale](#never-published-document) | `draft` | `never-published-document` | +| [Find modified documents](#modified) | `draft` or `published` | `modified` | +| [Find unmodified documents](#unmodified) | `draft` or `published` | `unmodified` | +| [Find published documents without a draft](#published-without-draft) | `published` | `published-without-draft` | +| [Find published documents with a draft](#published-with-draft) | `published` | `published-with-draft` | +| [Find documents with a published version](#has-published-version) | `draft` or `published` | `has-published-version` | +| [Find documents published in at least one locale](#has-published-version-document) | `draft` or `published` | `has-published-version-document` | :::note -Cohort definitions and `status` / `publicationFilter` combination tables are documented on [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). +Pairing a value with the opposite `status` from the table above is valid but returns nothing rather than an error: for example, `never-published` with `status=published` returns an empty result, because these documents have no published version yet. ::: -Accepted kebab-case values: `never-published`, `has-published-version`, `modified`, `unmodified`, `never-published-document`, `has-published-version-document`, `published-without-draft`, `published-with-draft`. Invalid values return HTTP `400`. +## Examples + +The following section lists the most common use cases summed up in the [table](#use-cases) above. -## Get never-published draft documents {#never-published} +### Find never published drafts {#never-published} -Pair-scoped `never-published` only matches draft rows. Pass `status=draft` because REST defaults to `status=published`. +One of the most common use cases is to find the drafts that have never been published. To do so, pass `status=draft` and `publicationFilter=never-published`. + +This parameter combination works only on a given locale; to find these documents across all locales, [use `never-published-document`](#never-published-document) instead. #### GET /api/restaurants?status=draft&publicationFilter=never-published — Get draft restaurants that have never been published for their locale -**REST:** +Return the drafts that have never been published for their locale. + +**cURL:** ``` -GET /api/restaurants?status=draft&publicationFilter=never-published +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=never-published' \\ + -H 'Authorization: Bearer ' ``` **JavaScript:** ``` const qs = require('qs'); const query = qs.stringify({ - status: 'draft', - publicationFilter: 'never-published', + status: 'draft', + publicationFilter: 'never-published', + }, { + encodeValuesOnly: true, // prettify URL +}); + +await request(\`/api/restaurants?\${query}\`); +``` + +**Response 200 OK:** +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "New Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + +### Find drafts never published in any locale {#never-published-document} + +`publicationFilter=never-published-document` returns documents that have never been published in any locale. It looks at the whole document across all its locales, not one locale at a time. To find these documents for a given locale only, [use `never-published`](#never-published) instead. + +A document counts as published as soon as one of its locales is published: the document is then left out, even the locales that only exist as a draft. The example below returns the draft versions of documents that were never published anywhere: + +#### GET /api/restaurants?status=draft&publicationFilter=never-published-document — Get drafts of restaurants never published in any locale + +Return the drafts of documents never published in any locale. + +**cURL:** +``` +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=never-published-document' \\ + -H 'Authorization: Bearer ' +``` + +**JavaScript:** +``` +const qs = require('qs'); +const query = qs.stringify({ + status: 'draft', + publicationFilter: 'never-published-document', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`); @@ -14118,43 +14505,96 @@ await request(\`/api/restaurants?\${query}\`); **Response 200 OK:** ```json { - "data": [ - { - "documentId": "a1b2c3d4e5f6g7h8i9j0klm", - "name": "New Restaurant", - "publishedAt": null, - "locale": "en" + "data": [ + { + "documentId": "d41r46wac4xix5vpba7561at", + "name": "New Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 +} +``` + +### Find modified documents {#modified} + +`publicationFilter=modified` selects documents whose draft has modified but unpublished changes. `status` then decides which version of those documents you get back. + +For instance, with `status=draft`, the query returns the draft versions: + +#### GET /api/restaurants?status=draft&publicationFilter=modified — Get the draft versions of modified restaurants + +Return the draft versions of documents with unpublished changes. + +**cURL:** +``` +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=modified' \\ + -H 'Authorization: Bearer ' +``` + +**JavaScript:** +``` +const qs = require('qs'); +const query = qs.stringify({ + status: 'draft', + publicationFilter: 'modified', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(\`/api/restaurants?\${query}\`); +``` + +**Response 200 OK:** +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant (updated)", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } } - } } ``` -## Get modified documents {#modified} +
+With `status=published` (the REST default), the same query returns the currently live version of those documents instead: -The `modified` cohort includes pairs where the draft row is newer than its published peer. With no `status` parameter, REST returns **published** rows from that cohort. Pass `status=draft` to return the draft rows instead. +#### GET /api/restaurants?publicationFilter=modified — Get the currently live version of modified restaurants -#### GET /api/restaurants?publicationFilter=modified — Get published restaurants in the modified cohort (default status) +Return the currently live versions of documents with unpublished changes. -**REST:** +**cURL:** ``` -GET /api/restaurants?publicationFilter=modified +curl 'http://localhost:1337/api/restaurants?publicationFilter=modified' \\ + -H 'Authorization: Bearer ' ``` **JavaScript:** ``` const qs = require('qs'); const query = qs.stringify({ - publicationFilter: 'modified', + publicationFilter: 'modified', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`); @@ -14163,43 +14603,96 @@ await request(\`/api/restaurants?\${query}\`); **Response 200 OK:** ```json { - "data": [ - { - "documentId": "znrlzntu9ei5onjvwfaalu2v", - "name": "Biscotte Restaurant", - "publishedAt": "2024-03-14T15:40:45.330Z", - "locale": "en" + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 +} +``` + +### Find unmodified documents {#unmodified} + +`publicationFilter=unmodified` selects documents whose draft has not changed since it was last published. `status` then decides which version of those documents you get back. + +For instance, with `status=draft`, the query returns the draft versions: + +#### GET /api/restaurants?status=draft&publicationFilter=unmodified — Get the draft versions of unmodified restaurants + +Return the draft versions of documents unchanged since their last publication. + +**cURL:** +``` +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=unmodified' \\ + -H 'Authorization: Bearer ' +``` + +**JavaScript:** +``` +const qs = require('qs'); +const query = qs.stringify({ + status: 'draft', + publicationFilter: 'unmodified', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(\`/api/restaurants?\${query}\`); +``` + +**Response 200 OK:** +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } } - } } ``` -## Get published rows without a draft peer {#published-without-draft} +
+With `status=published` (the REST default), the same query returns the currently live version of those documents instead: -The `published-without-draft` cohort matches published rows that have no draft sibling for the same `(documentId, locale)`. Because REST defaults to `status=published`, you can omit `status` in the query URL. +#### GET /api/restaurants?publicationFilter=unmodified — Get the currently live version of unmodified restaurants -#### GET /api/restaurants?publicationFilter=published-without-draft — Get published restaurants with no draft row for the same locale +Return the currently live versions of documents unchanged since their last publication. -**REST:** +**cURL:** ``` -GET /api/restaurants?publicationFilter=published-without-draft +curl 'http://localhost:1337/api/restaurants?publicationFilter=unmodified' \\ + -H 'Authorization: Bearer ' ``` **JavaScript:** ``` const qs = require('qs'); const query = qs.stringify({ - publicationFilter: 'published-without-draft', + publicationFilter: 'unmodified', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/restaurants?\${query}\`); @@ -14208,22 +14701,265 @@ await request(\`/api/restaurants?\${query}\`); **Response 200 OK:** ```json { - "data": [ - { - "documentId": "abcdefghijklmno456", - "name": "Legacy Restaurant", - "publishedAt": "2024-01-10T09:15:00.000Z", - "locale": "en" + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } } - ], - "meta": { - "pagination": { - "page": 1, - "pageSize": 25, - "pageCount": 1, - "total": 1 +} +``` + +### Find published documents without a draft {#published-without-draft} + +`publicationFilter=published-without-draft` selects published documents that have no draft counterpart. It describes published rows, so REST returns them with the default `status=published`: + +#### GET /api/restaurants?publicationFilter=published-without-draft — Get published restaurants with no draft for the same locale + +Return published documents with no matching draft version for the same locale. + +**cURL:** +``` +curl 'http://localhost:1337/api/restaurants?publicationFilter=published-without-draft' \\ + -H 'Authorization: Bearer ' +``` + +**JavaScript:** +``` +const qs = require('qs'); +const query = qs.stringify({ + publicationFilter: 'published-without-draft', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(\`/api/restaurants?\${query}\`); +``` + +**Response 200 OK:** +```json +{ + "data": [ + { + "documentId": "j0klm1n2o3p4q5r6s7t8u9v", + "name": "Legacy Restaurant", + "publishedAt": "2024-01-10T09:15:00.000Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + +### Find published documents with a draft {#published-with-draft} + +`publicationFilter=published-with-draft` selects published documents that also have a draft. It describes published rows, so REST returns them with the default `status=published`: + +#### GET /api/restaurants?publicationFilter=published-with-draft — Get published restaurants that also have a draft for the same locale + +Return published documents that also have a matching draft version for the same locale. + +**cURL:** +``` +curl 'http://localhost:1337/api/restaurants?publicationFilter=published-with-draft' \\ + -H 'Authorization: Bearer ' +``` + +**JavaScript:** +``` +const qs = require('qs'); +const query = qs.stringify({ + publicationFilter: 'published-with-draft', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(\`/api/restaurants?\${query}\`); +``` + +**Response 200 OK:** +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + +### Find documents with a published version {#has-published-version} + +`publicationFilter=has-published-version` selects documents that have both a draft and a published version for the same locale. `status` then decides which version of those documents you get back. Unlike `published-without-draft`, it excludes published documents that have no draft counterpart. + +For instance, with `status=draft`, the query returns the draft versions: + +#### GET /api/restaurants?status=draft&publicationFilter=has-published-version — Get the draft versions of restaurants that also have a published version + +Return the draft versions of documents that also have a published version for the same locale. + +**cURL:** +``` +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=has-published-version' \\ + -H 'Authorization: Bearer ' +``` + +**JavaScript:** +``` +const qs = require('qs'); +const query = qs.stringify({ + status: 'draft', + publicationFilter: 'has-published-version', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(\`/api/restaurants?\${query}\`); +``` + +**Response 200 OK:** +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + +
+With `status=published` (the REST default), the same query returns the currently live version of those documents instead: + +#### GET /api/restaurants?publicationFilter=has-published-version — Get the currently live version of restaurants that also have a draft + +Return the currently live versions of documents that also have a published version for the same locale. + +**cURL:** +``` +curl 'http://localhost:1337/api/restaurants?publicationFilter=has-published-version' \\ + -H 'Authorization: Bearer ' +``` + +**JavaScript:** +``` +const qs = require('qs'); +const query = qs.stringify({ + publicationFilter: 'has-published-version', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(\`/api/restaurants?\${query}\`); +``` + +**Response 200 OK:** +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": "2024-03-14T15:40:45.330Z", + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } + } +} +``` + +### Find documents published in at least one locale {#has-published-version-document} + +`publicationFilter=has-published-version-document` considers all locales, so it matches a document as soon as one of its locales is published. With `status=draft`, it returns the draft versions of every locale of those documents, including locales that were never published themselves: + +#### GET /api/restaurants?status=draft&publicationFilter=has-published-version-document — Get the drafts of restaurants published in at least one locale + +Return the draft versions of documents published in at least one locale. + +**cURL:** +``` +curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=has-published-version-document' \\ + -H 'Authorization: Bearer ' +``` + +**JavaScript:** +``` +const qs = require('qs'); +const query = qs.stringify({ + status: 'draft', + publicationFilter: 'has-published-version-document', +}, { + encodeValuesOnly: true, // prettify URL +}); + +await request(\`/api/restaurants?\${query}\`); +``` + +**Response 200 OK:** +```json +{ + "data": [ + { + "documentId": "a1b2c3d4e5f6g7h8i9j0klm", + "name": "Biscotte Restaurant", + "publishedAt": null, + "locale": "en" + } + ], + "meta": { + "pagination": { + "page": 1, + "pageSize": 25, + "pageCount": 1, + "total": 1 + } } - } } ``` @@ -14649,7 +15385,7 @@ The sorting order can be defined with: You can sort by multiple fields by passing fields in a `sort` array. -#### GET /api/restaurants — Sort using 2 fields +#### GET /api/restaurants?sort[0]=Description&sort[1]=Name — Sort using 2 fields Sort results by Description and Name fields. @@ -14720,7 +15456,7 @@ await request(\`/api/restaurants?\${query}\`); Using the `sort` parameter and defining `:asc` or `:desc` on sorted fields, you can get results sorted in a particular order. -#### GET /api/restaurants — Sort using 2 fields and set the order +#### GET /api/restaurants?sort[0]=Description:asc&sort[1]=Name:desc — Sort using 2 fields and set the order Sort results by Description ascending and Name descending. @@ -14808,7 +15544,7 @@ To paginate results by page, use the following parameters: | `pagination[pageSize]` | Integer | Page size | 25 | | `pagination[withCount]` | Boolean | Adds the total numbers of entries and the number of pages to the response | True | -#### GET /api/articles — Pagination by page +#### GET /api/articles?pagination[page]=1&pagination[pageSize]=10 — Pagination by page Return only 10 entries on page 1. @@ -14863,7 +15599,7 @@ To paginate results by offset, use the following parameters: The default and maximum values for `pagination[limit]` can be [configured in the `./config/api.js`](/cms/configurations/api) file with the `api.rest.defaultLimit` and `api.rest.maxLimit` keys. ::: -#### GET /api/articles — Pagination by offset +#### GET /api/articles?pagination[start]=0&pagination[limit]=10 — Pagination by offset Return only the first 10 entries using offset. @@ -14931,7 +15667,7 @@ In the response data, the `publishedAt` field is `null` for drafts. Since published versions are returned by default, passing no status parameter is equivalent to passing `status=published`. ::: -For derived publication cohorts (never-published, modified, and others), see [REST API: `publicationFilter`](/cms/api/rest/publication-filter). +To select documents by how their draft and published versions relate (never-published, modified, and others), see [REST API: `publicationFilter`](/cms/api/rest/publication-filter).

@@ -14939,13 +15675,19 @@ For derived publication cohorts (never-published, modified, and others), see [RE Returns draft versions of documents by passing the status=draft query parameter. +**cURL:** +``` +curl 'http://localhost:1337/api/articles?status=draft' \\ + -H 'Authorization: Bearer ' +``` + **JavaScript:** ``` const qs = require('qs'); const query = qs.stringify({ - status: 'draft', + status: 'draft', }, { - encodeValuesOnly: true, // prettify URL + encodeValuesOnly: true, // prettify URL }); await request(\`/api/articles?\${query}\`); @@ -32493,17 +33235,17 @@ To unpublish several entries at the same time: ### Usage with APIs -Draft or published content can be requested, created, updated, and deleted using the `status` parameter through the various front-end APIs accessible from [Strapi's Content API](/cms/api/content-api). To query derived cohorts such as never-published or modified documents, use the `publicationFilter` parameter (REST and GraphQL) or the equivalent Document Service API option. +Draft or published content can be requested, created, updated, and deleted using the `status` parameter through the various front-end APIs accessible from [Strapi's Content API](/cms/api/content-api). To query documents by the relationship between their draft and published versions, such as never-published or modified documents, use the `publicationFilter` parameter (REST and GraphQL) or the equivalent Document Service API option. - [REST API](/cms/api/rest/status): Learn how to use the status parameter with the REST API. -- [REST API: publicationFilter](/cms/api/rest/publication-filter): Query never-published, modified, and other derived publication cohorts. +- [REST API: publicationFilter](/cms/api/rest/publication-filter): Query never-published, modified, and other publication-related groups of documents. - [GraphQL API](/cms/api/graphql#status): Learn how to use the status parameter with GraphQL API. -- [GraphQL API: publicationFilter](/cms/api/graphql#publication-filter): Query derived publication cohorts with the PublicationFilter enum. +- [GraphQL API: publicationFilter](/cms/api/graphql#publication-filter): Query never-published, modified, and more with the PublicationFilter enum. On the back-end server of Strapi, the Document Service API can also query and manage draft and published content: - [Document Service API](/cms/api/document-service/status): Learn how to use the status parameter with the Document Service API. -- [Document Service API: publicationFilter](/cms/api/document-service/publication-filter): Query derived publication cohorts from server-side code. +- [Document Service API: publicationFilter](/cms/api/document-service/publication-filter): Query never-published, modified, and more from server-side code. diff --git a/docusaurus/static/llms.txt b/docusaurus/static/llms.txt index 1317221176..7cfb058b14 100644 --- a/docusaurus/static/llms.txt +++ b/docusaurus/static/llms.txt @@ -44,7 +44,7 @@ - [Using the locale parameter with the Document Service API](https://docs.strapi.io/cms/api/document-service/locale.md): The locale parameter in the Document Service API lets you query, create, update, delete, publish, and unpublish documents for specific language versions using methods like findOne(), findMany(), update(), and delete(). - [Extending the Document Service behavior](https://docs.strapi.io/cms/api/document-service/middlewares.md): Document Service middlewares allow you to perform actions before and after Document Service methods run by registering middleware functions via strapi.documents.use() with access to content type context and method parameters. - [Using Populate with the Document Service API](https://docs.strapi.io/cms/api/document-service/populate.md): Use the populate parameter with the Document Service API to explicitly load relations, media fields, components, and dynamic zones at one or multiple levels deep, and within create(), update(), publish(), and delete() operations. -- [Using publicationFilter with the Document Service API](https://docs.strapi.io/cms/api/document-service/publication-filter.md): Use the publicationFilter parameter with Strapi's Document Service API to query derived Draft & Publish cohorts such as never-published or modified documents. +- [Using publicationFilter with the Document Service API](https://docs.strapi.io/cms/api/document-service/publication-filter.md): Use the optional publicationFilter parameter to query documents by the relationship between their draft and published versions, for example drafts that were never published, or entries modified since they were last published. It works with findOne(), findFirst(), findMany(), and count(), and combines with other query parameters. status still decides whether you get the draft or the published version. - [Using Sort & Pagination with the Document Service API](https://docs.strapi.io/cms/api/document-service/sort-pagination.md): Use the Document Service API's sort and pagination parameters to order query results by single or multiple fields and control result limits with limit and start. - [Using Draft & Publish with the Document Service API](https://docs.strapi.io/cms/api/document-service/status.md): Use the status parameter with the Document Service API to retrieve published or draft versions of documents, count documents by status, and directly publish documents during creation or updates. - [Entity Service API](https://docs.strapi.io/cms/api/entity-service.md): The Entity Service API is a backend layer that handles complex content structures like components and dynamic zones, providing CRUD operations, filtering, populating relations, and pagination via strapi.entityService. @@ -73,7 +73,7 @@ - [Locale](https://docs.strapi.io/cms/api/rest/locale.md): The locale REST API parameter retrieves and manages documents in specific languages, defaulting to the application's default locale. Use it to fetch, create, update, and delete locale-specific versions of documents in both collection and single types. - [Parameters](https://docs.strapi.io/cms/api/rest/parameters.md): REST API parameters filter, sort, paginate, and select fields and relations in Strapi queries. Use filters, locale, populate, sort, and pagination to refine your content requests. - [Populate and Select](https://docs.strapi.io/cms/api/rest/populate-select.md): Use the populate parameter to include relations, media fields, components, and dynamic zones in REST API responses. Use the fields parameter to return only specific fields. -- [Publication filter](https://docs.strapi.io/cms/api/rest/publication-filter.md): Use the publicationFilter parameter with Strapi's REST API to query derived Draft & Publish cohorts such as never-published or modified documents. +- [REST API: publicationFilter](https://docs.strapi.io/cms/api/rest/publication-filter.md): Add the optional publicationFilter query parameter to query documents by the relationship between their draft and published versions, for example documents that were never published, or documents modified since they were last published. It combines with other query parameters, and status still decides whether you get the draft or the published version. - [Relations](https://docs.strapi.io/cms/api/rest/relations.md): Use connect, disconnect, and set parameters in REST and GraphQL API requests to manage relations between content-types. Reorder relations using positional arguments like before, after, start, or end. - [Sort and Pagination](https://docs.strapi.io/cms/api/rest/sort-pagination.md): Sort REST API results on one or multiple fields with :asc or :desc syntax, and paginate using either page-based or offset-based parameters. - [Status](https://docs.strapi.io/cms/api/rest/status.md): The REST API's status parameter filters documents by their publication state, returning either published versions (default) or drafts by passing status=draft.