| title |
aliases |
tags |
sources |
created |
updated |
| Search Plugin |
| plugin-search |
| payload-search |
| search-index |
|
| payloadcms |
| plugin |
| search |
| indexing |
|
|
2026-05-15 |
2026-05-15 |
Overview
@payloadcms/plugin-search creates a dedicated search collection that mirrors only search-critical fields from your enabled collections. Records are automatically synced on document create/update/delete via hooks, and can be queried with standard Payload APIs — no third-party service required.
How It Works
- Plugin adds a
search collection to your database with an index
- Enabled collections get
beforeChange + afterDelete hooks
- On save → a lightweight search record (title, excerpt, slug, etc.) is created/updated
- On delete → search record is removed
- Queries hit the
search collection directly — bypassing all hooks on the original collection
Installation
pnpm add @payloadcms/plugin-search
Basic Setup
import { searchPlugin } from '@payloadcms/plugin-search'
export default buildConfig({
plugins: [
searchPlugin({
collections: ['pages', 'posts'],
defaultPriorities: {
pages: 10,
posts: 20,
},
}),
],
})
Options Reference
| Option |
Type |
Default |
Description |
collections |
string[] |
required |
Slugs of collections to index |
localize |
boolean |
true |
Add localization to title field if i18n is enabled |
defaultPriorities |
Record<slug, number | fn> |
— |
Fallback priority on create; fn receives doc |
searchOverrides |
Partial<CollectionConfig> |
— |
Merge into the auto-created search collection config |
beforeSync |
afterChange hook |
— |
Modify/fallback search doc data before create/update |
syncDrafts |
boolean |
false |
Sync draft documents to search index |
deleteDrafts |
boolean |
true |
Delete search record when doc status changes to draft |
skipSync |
async fn |
— |
Return true to skip a specific doc/locale combination |
reindexBatchSize |
number |
50 |
Batch size for on-demand reindexing |
beforeSync — Modify Data Before Index
searchPlugin({
beforeSync: ({ originalDoc, searchDoc }) => ({
...searchDoc,
excerpt: originalDoc?.excerpt || 'Fallback excerpt',
}),
})
searchOverrides — Extend the Search Collection
searchPlugin({
searchOverrides: {
slug: 'search-results',
fields: ({ defaultFields }) => [
...defaultFields,
{ name: 'excerpt', type: 'textarea' },
],
},
})
skipSync — Conditional Indexing
searchPlugin({
skipSync: async ({ locale, doc, collectionSlug, req }) => {
if (!locale) return false
const tenant = await req.payload.findByID({ collection: 'tenants', id: doc.tenant.id })
return !tenant.allowedLocales.includes(locale)
},
})
skipSync is called once per locale per document. Return true = skip, false = proceed.
Priority & Sorting
- Every search record gets a
priority field
- Use
?sort=priority (ascending) to order results by collection type
defaultPriorities sets the initial value at create time
- Can be a static number or a function:
({ doc }) => doc.featured ? 1 : 10
On-Demand Reindexing
- Navigate to the
search collection in the Admin Panel
- Click the Reindex pill (top-right action slot)
- Select a specific collection or "all" to rebuild indexes
- Useful when adding the plugin to an existing project with existing documents
TypeScript
import type { SearchConfig, BeforeSync } from '@payloadcms/plugin-search/types'
Key Takeaways
- First-party Algolia alternative — no external service; runs on your own DB
- Static snapshots bypass hooks — queries on
search collection don't trigger hooks on original docs, making them significantly faster
- You control what's indexed — only sync fields you need (title, slug, excerpt)
- Priority field enables ranked results — pass
?sort=priority to order by collection or document importance
skipSync is per locale — called once per locale per document; useful for multi-tenant locale filtering
- Draft handling is opt-in —
syncDrafts: false by default; set deleteDrafts: true to remove drafts from search
- Reindex button covers existing data migration when adding plugin to existing projects
Related
Sources