Dosu LogoDosu Logo
Ask
Join our Discord
StrapiPublic
Strapi
DocumentsStrapi
Content Manager URL & Filter State
Content Manager URL & Filter State
Type
Topic
Status
Published
Created
Aug 3, 2026
Updated
Aug 3, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Content Manager URL & Filter State#

Overview#

The Content Manager's ListView encodes all view state — filters, pagination, sort, locale, and other plugin parameters — in the URL query string. This makes individual views deep-linkable and bookmarkable. State flows from user interaction → URL update → API request in a unidirectional pattern mediated by useQueryParams and a qs-based serialization layer.


URL Shape#

The query string uses the qs library for serialization/deserialization, enabling deeply nested objects (e.g., filters[$and][0][status][$eq]=draft). The top-level keys recognised by the ListView are typed as ListViewQuery:

KeyPurpose
filters.$andArray of filter objects, each { [field]: { [operator]: value } }
plugins.*Plugin-specific params (e.g., plugins.i18n.locale); stripped before API calls
pageCurrent page number (string)
pageSizeEntries per page (string)
sortSort expression, e.g., "name:ASC" or "createdAt:DESC"

Core Hook: useQueryParams#

useQueryParams (packages/core/admin/admin/src/hooks/useQueryParams.ts) is the shared foundation:

  • Reads the current location.search, stripping any leading ?, and merges it with optional initialParams via qs.parse .
  • Writes via setQuery(nextParams, method, replace):
    • method='push' (default) merges nextParams into the current query.
    • method='remove' deletes the specified keys.
    • Calls navigate({ search: stringify(nextQuery, { encode: false }) }, { replace }) .
  • Returns [{ query, rawQuery }, setQuery].

The ListView calls it with defaults for page, pageSize, and sort .


Persistent State: usePersistentPartialQueryParams#

usePersistentPartialQueryParams (packages/core/content-manager/admin/src/hooks/usePersistentQueryParams.ts) layers localStorage persistence on top of useQueryParams:

  • Accepts a PersistentQueryConfig — a map of storage keys to { paths, scoped? }. Keys scoped to a model are namespaced KEY:modelUID .
  • On mount, reads matching keys from localStorage, deep-merges with URL params (URL wins), and navigates to the merged state. Sets isHydrated=true after this .
  • On query change, serializes only the configured paths back to localStorage .
  • Returns { isHydrated }. The ListView delays the API fetch until isHydrated is true to avoid a flash of un-restored state .

The ListView configures two persistent entries :

STRAPI_LIST_VIEW_SETTINGS:<model> → paths: ['sort', 'filters', 'pageSize'], scoped: true
STRAPI_LOCALE → paths: ['plugins.i18n.locale'], scoped: false

Filter State Flow#

Loading diagram...

Filter Serialization (Popover → URL)#

Filters.Popover.handleSubmit builds the filter entry:

  • Values are encodeURIComponent-encoded (filter-free operators like $null get "true" instead).
  • Relations are nested: { [field]: { [mainField|id]: { [op]: value } } }.
  • The new entry is appended (or replaces when editing) in query.filters.$and, then pushed to the URL via setQuery({ filters: newFilterQuery, page: 1 }, 'push', true) .

Filter Deserialization (URL → display)#

Filters.List reads query.filters.$and and renders an AttributeTag for each entry. Values are decoded with decodeURIComponent before display . Clicking a tag removes it from $and and resets page to 1.

Schema-Aware Filter Options#

The ListView wraps Filters with a custom listViewFilters.Root that:

  1. Derives a displayedFilters array from the content type schema and layout.
  2. Adds id, documentId, and (for D&P content types) a special __status filter .
  3. Runs the INJECT_LIST_VIEW_FILTERS hook waterfall so plugins can add filters .
  4. Passes the resolved filter options into the base Filters.Root as options.

Status Sort Interaction#

When a __status filter is active, the status column sort is disabled. If sort=status:* is already in the URL when a status filter is applied, an effect in ListViewPage strips it from the query .


URL → API Request: buildValidParams#

Before calling the API, the raw query is passed through buildValidParams (packages/core/content-manager/admin/src/utils/api.ts):

  • Removes the plugins key.
  • Promotes each plugin's values to the root level (e.g., plugins.i18n.locale=en → locale=en).
  • The resulting flat object is sent as API query params to useGetAllDocumentsQuery .

Edge Cases & Known Fixes#

  • Trailing ? in URLs: When qs.stringify({}) returns "", the paramsSerializer in getFetchClient.ts was previously emitting ?, which broke publish deduplication guards. Fixed by skipping the ? when the serialized result is empty .
  • Hydration guard: API calls are skipped while isHydrated=false to prevent the page loading before localStorage state is restored .
  • Over-range pagination: If the API returns a pagination.page beyond pagination.pageCount, the page is reset to pageCount via a navigate({ replace: true }) .
  • Plugin params propagation: When navigating to create/edit entries, only query.plugins is forwarded (not sort/filters), preserving locale without carrying filter state into the edit view .

Key Files#

FileRole
ListViewPage.tsxTop-level orchestration: query defaults, hydration guard, API call, status-sort interaction
useQueryParams.tsqs-based URL read/write hook used across the admin
usePersistentQueryParams.tslocalStorage persistence layer for URL params
Filters.tsx (admin)Generic filter UI: Root, Trigger, Popover, List
Filters.tsx (content-manager)Schema-aware filter wrapper with listViewFilters export
api.tsbuildValidParams — strips plugins.*, promotes plugin values to root
Documents
Admin Modal Management
Admin Panel Locale Management
admin-panel
faq
setting-up-admin-panel
Admin User Management
Blocks Editor
Content Manager i18n Preview
locale
Content Manager Layout Synchronization
breaking-changes
Content Manager Preview
breaking-changes
Content Manager RBAC
Content Manager URL & Filter State
admin-panel-api
content-manager
Content Type Builder
breaking-changes
content-type-builder
controllers
create-components-for-plugins
models
populate-creator-fields
quick-start
store-and-access-data
Custom Field Prop Spreading
custom-fields
extension
Database Migration Concurrency and Idempotency
step-by-step
Date Field Serialization
Discard-Drafts Migration
Document ID Migration
breaking-changes
step-by-step
Draft & Publish Relation Synchronization
do-not-update-repeatable-components-with-document-service-api
document-service
lifecycle-hooks-document-service
populate
publishedat-always-set-when-dandp-disabled
relations
status
DynamicZone Stability
breaking-changes
components-and-dynamic-zones-do-not-return-id
components-dynamic-zones
no-shared-population-strategy-components-dynamic-zones
Edit View Layout Configuration
admin-panel
breaking-changes
edit-view-layout-and-list-view-layout-rewritten
EnumerationInput Component
GraphQL Context Propagation
document-service
draft-and-publish
i18n Locale Validation
breaking-changes
locale
locale
i18n Locale-Scoped Operations
breaking-changes
crud
database-columns
document
document-service
draft-and-publish
i18n-content-manager-locale
locale
locale
locale
no-locale-all
parameters
populate
relations
Monorepo Module Resolution
Nested Relation Modal Navigation
Relation Field Validation
Relation Modal State Management
relations
Sharp Image Processing
media-library
Strapi Data Transfer
cli
Strapi Project Scaffolding
cli
create-a-plugin
How can I create my first Strapi project on a brand new MacBook with no prior coding experience, including all necessary terminal commands and setup steps?
quick-start
Strapi v5 Plugin API
admin-panel-rbac-store-updated
admin-permissions-for-plugins
breaking-changes
controllers
faq
get-where-removed
helper-plugin
helper-plugin-deprecated
inject-content-manager-component
introduction
introduction-and-faq
model-config-path-uses-uid
pass-data-from-server-to-admin
plugins-migration
rbac
redux-content-manager-app-state
remove-webhook-populate-relations
strapi-imports
Users-Permissions Plugin i18n
users-permissions
Vite Build Configuration
admin-panel
introduction-and-faq
vite
webpack-aliases-removed
Content Manager Mobile and Tablet UI Improvements
FilesManager API Reference
Focal Point Picker in Strapi Media Library
Nested Route File Structure for Strapi Plugins
Plugin-Specific Handling in the Strapi JavaScript Client
Strapi SDK Initialization and Architecture Guide
access-cast-environment-variables
access-configuration-values
adding-support-to-existing-project
admin-panel-customization
advanced-policies
advanced-queries
amazon-s3
api
api-tokens
attributes-and-content-types-names-reserved
audit-logs
auth-zero
authentication
aws-cognito
backend-customization
bulk-operations
bundlers
cas
cli
client
cloudinary
community
configurations
configure-sso
content-api
content-history
content-manager-apis
core-service-methods-use-document-service
cron
customization
data-management
database
database-identifiers-shortened
database-migrations
database-transactions
default-index-removed
default-input-validation
deployment
design-system
developing-plugins
development
discord
discord
docker
documentation
documents-and-entries
email
email-custom-providers
email-nodemailer
entity-service
entity-service-deprecated
environment
error-handling
examples
facebook
favicon
features
fetch
fields
filter
filtering
filters
filters
from-entity-service-to-document-service
functions
github
github
google
google
graphql
graphql
graphql-api-updated
guides
homepage
host-port-path
instagram
installation
installing-plugins-via-marketplace
interactive-query-builder
internationalization
intro
intro
is-supported-image-removed
keycloak
keycloak
koa-body-v6
license-only
linkedin
local-upload
locales-translations
logos
mailgun-provider-variables
media-library-providers
microsoft
middlewares
middlewares
middlewares
middlewares
mysql5-unsupported
new-provider-guide
new-response-format
no-find-page-in-document-service
no-upload-at-entry-creation
okta
only-better-sqlite3-for-sqlite
only-mysql2-package-for-mysql
openapi
order-pagination
order-pagination
patreon
plugin-sdk
plugin-structure
plugins
plugins-extension
policies
policies
populate
populate-select
populating
preview
project-structure
publication-state-removed
query-engine
rbac
react-router-dom-6
reddit
register-allowed-fields
releases
removed-support-for-some-env-options
requests-responses
rest
review-workflows
routes
routes
sentry
server
server-api
server-default-log-level
server-proxy
services
services-and-controllers
setup-deployment
single-operations
sort-by-id
sort-pagination
sort-pagination
sso
status
strapi-container
strapi-utils-refactored
strict-requirements-config-files
templates
templates
testing
theme-extension
twitch
twitter
typescript
typescript
understanding-populate
upgrade-to-apollov4
upgrade-tool
upgrades
upload
usage-information
use-document-id
users-and-permissions-providers
vk
webhooks
wysiwyg-editor
yarn-not-default