Skip to main content

Document Service API: publicationFilter

The 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) 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 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 and 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

publicationFilter is applied after status is resolved (explicitly or by default). Defaults differ by API surface:

API surfaceDefault status when omitted
Document Service API (direct)'draft'
REST API'published'
GraphQL APIPUBLISHED

The following example compares Document Service and REST behavior when only publicationFilter: 'modified' is passed:

// 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

REST and the Document Service API use kebab-case strings. GraphQL exposes the same cohorts through the PublicationFilter enum.

ValueScopeCohort definition (which (documentId, locale) pairs match)
never-publishedPairNo row with non-null publishedAt exists for the same (documentId, locale)
has-published-versionPairBoth a draft row and a published row exist for the same (documentId, locale)
modifiedPairBoth slices exist and draft.updatedAt > published.updatedAt
unmodifiedPairBoth slices exist and draft.updatedAt <= published.updatedAt
never-published-documentDocumentNo row with non-null publishedAt exists for the same documentId in any locale
has-published-version-documentDocumentAt least one published row exists for the same documentId in any locale
published-without-draftPairA published row exists for the pair and no draft row exists for the same (documentId, locale)
published-with-draftPairA 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.

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

Pass status explicitly or rely on the default for your API surface. Each table lists which rows a publicationFilter returns for that status.

With status: 'draft'

publicationFilterRows returned
never-publishedDraft rows for pairs never published in that locale
has-published-versionDraft rows for pairs that also have a published version
modifiedDraft rows newer than their published peer
unmodifiedDraft rows not newer than their published peer
never-published-documentDraft rows whose document has no published row in any locale
has-published-version-documentDraft rows whose document has at least one published row (any locale)
published-without-draft, published-with-draftNo rows

With status: 'published'

publicationFilterRows returned
has-published-versionPublished rows for pairs that also have a draft version (excludes orphan published-only pairs)
modifiedPublished rows whose draft peer is newer
unmodifiedPublished rows whose draft peer is not newer
has-published-version-documentPublished rows whose document has at least one draft row (any locale)
published-without-draftPublished rows with no draft sibling for the same pair
published-with-draftPublished rows that have a draft sibling for the same pair
never-published, never-published-documentNo rows
Note

Valid but empty combinations do not return validation errors.

Examples

Never-published and modified cohorts

// Pair-scoped: 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
await strapi.documents('api::restaurant.restaurant').findMany({
status: 'draft',
publicationFilter: 'modified',
});

await strapi.documents('api::restaurant.restaurant').findMany({
status: 'published',
publicationFilter: 'modified',
});

Document-scoped cohorts

// 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 rows with or without a draft peer

published-without-draft and published-with-draft require status: 'published'.

await strapi.documents('api::restaurant.restaurant').findMany({
status: 'published',
publicationFilter: 'published-without-draft',
});

await strapi.documents('api::restaurant.restaurant').findMany({
status: 'published',
publicationFilter: 'published-with-draft',
});

Use with findOne() and findFirst()

If the requested document (and locale, when applicable) is not in the cohort, findOne() and findFirst() return null even when the documentId exists:

await strapi.documents('api::restaurant.restaurant').findOne({
documentId: 'a1b2c3d4e5f6g7h8i9j0klm',
status: 'draft',
publicationFilter: 'never-published',
});

Count documents in a 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).

const neverPublishedCount = await strapi
.documents('api::restaurant.restaurant')
.count({
status: 'draft',
publicationFilter: 'never-published',
});
Was this page helpful?