Syncs selected Payload collections to an Algolia index whenever documents are created, updated or deleted. It adds a search endpoint and a re-index endpoint, plus a re-index button in the admin panel. Search results can optionally be enriched with fresh, access-controlled documents from Payload. Requires Payload 3.37 or later and an Algolia admin API key; localized fields are not supported.
Algolia Search Plugin for Payload CMS
Installation
npm i @veiag/payload-algolia-search1
Stars
0
Forks
0
Open Issues
1 years ago
Last Updated
0
Weekly Downloads
v1.0.3
npm Version
94.8 KB
Unpacked Size
2
Dependencies
Trends appear after 7 days of data. Daily snapshots have not started yet.
Trends appear after 7 days of data. Daily snapshots have not started yet.
Each point is npm's rolling 7-day download count.
Health Score
Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.
Badge for your README
[](https://payloaddirectory.dev/plugins/1035672939-root)<a href="https://payloaddirectory.dev/plugins/1035672939-root"><img src="https://payloaddirectory.dev/badge/1035672939-root.svg" alt="Payload Directory"></a>README
A powerful, feature-rich plugin to sync your Payload CMS collections with Algolia to enable fast and extensive search capabilities.
The Payload Algolia Search Plugin bridges your Payload CMS with Algolia's powerful search infrastructure, providing:
Install the plugin using your preferred package manager:
# pnpm (recommended)
pnpm add @veiag/payload-algolia-search
# npm
npm install @veiag/payload-algolia-search
# yarn
yarn add @veiag/payload-algolia-search
Add the plugin to your payload.config.ts:
import { buildConfig } from 'payload/config'
import { algoliaSearchPlugin } from '@veiag/payload-algolia-search'
export default buildConfig({
// ... your existing config
plugins: [
algoliaSearchPlugin({
credentials: {
appId: process.env.ALGOLIA_APP_ID!,
apiKey: process.env.ALGOLIA_API_KEY!, // Admin API Key
indexName: process.env.ALGOLIA_INDEX_NAME!,
},
collections: [
{
slug: 'posts',
indexFields: ['title', 'content', 'tags'],
},
],
}),
],
})
Create a .env file with your Algolia credentials:
ALGOLIA_APP_ID=your_app_id
ALGOLIA_API_KEY=your_admin_api_key
ALGOLIA_INDEX_NAME=your_index_name
The plugin will automatically:
To index existing documents, use re-index endpoints (or button in admin UI)
The plugin accepts a configuration object with the following options:
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
credentials | PluginAlgoliaCredentials | ā | - | Algolia connection details |
collections | CollectionAlgoliaConfig[] | ā | - | Collections to sync with Algolia |
searchEndpoint | string | false | ā | '/search' | Search endpoint path (set to false to disable) |
overrideAccess | boolean | ā | false | If true, the plugin will override access control when enriching search results |
reindexEndpoint | string | false | ā | '/reindex' | Re-index endpoint path (set to false to disable) |
configureIndexOnInit | boolean | ā | true | Auto-configure Algolia index on startup |
hideReindexButton | boolean | ā | false | Hide re-index button in admin UI |
reindexAccess | function | ā | ( req ) => !!req.user | Access control for re-index operations |
fieldTransformers | Record<string, FieldTransformer> | ā | - | Custom field transformation functions |
disabled | boolean | ā | false | Disable the plugin entirely |
Each collection in the collections array supports:
interface CollectionAlgoliaConfig {
slug: string; // Collection slug
indexFields: string[]; // Fields to index in Algolia
}
| Variable | Description | Required |
|---|---|---|
ALGOLIA_APP_ID | Your Algolia Application ID | ā |
ALGOLIA_API_KEY | Your Algolia Admin/Write API Key | ā |
ALGOLIA_INDEX_NAME | Target Algolia index name | ā |
ā ļø Security Note: The
ALGOLIA_API_KEYshould be your Admin/Write API Key and must be kept secret. Never expose it in client-side code.
Perform search queries against your Algolia index.
Endpoint: GET /search (or your configured searchEndpoint)
| Parameter | Type | Description |
|---|---|---|
query | string | Search term |
enrichResults | boolean | Fetch fresh documents from Payload |
select | object | Field selection for enriched results |
depth | object | Depth options for enriched results |
hitsPerPage | number | Number of results per page |
filters | string | Algolia filters |
| Any other Algolia search parameter | varies | Passed directly to Algolia |
// Simple search
const response = await fetch('/search?query=javascript&hitsPerPage=10');
const results = await response.json();
Manually trigger a full re-index of a collection.
Endpoint: POST /reindex/:collectionSlug
// Re-index the 'posts' collection
const response = await fetch('/reindex/posts', { method: 'POST' });
const result = await response.json();
By default, search results come directly from Algolia for maximum speed. However, you can enable result enrichment to get fresh, access-controlled data from your Payload database.
Add enrichResults=true to your search query:
const response = await fetch('/search?query=javascript&enrichResults=true');
const { hits, enrichedHits, ...algoliaMetadata } = await response.json();
// hits: Original Algolia results with search metadata
// enrichedHits: Fresh documents from Payload (keyed by ID)
{
"hits": [
{
"objectID": "60c7c5d5f1d2a5001f6b0e3d",
"title": "JavaScript Basics",
"_highlightResult": { "title": { "value": "<em>JavaScript</em> Basics" } }
}
],
"enrichedHits": {
"60c7c5d5f1d2a5001f6b0e3d": {
"id": "60c7c5d5f1d2a5001f6b0e3d",
"title": "JavaScript Basics",
"content": "Full article content...",
"author": { "name": "John Doe" },
"updatedAt": "2024-01-15T10:30:00Z"
}
},
"nbHits": 1,
"page": 0
}
Control which fields are returned in enriched results to optimize response size and performance.
import qs from 'qs-esm';
// Include only specific fields
const selectFields = {
posts: { title: true, slug: true },
authors: { name: true, email: true }
};
const params = {
query: 'javascript',
enrichResults: true,
select: selectFields
};
const url = `/search?${qs.stringify(params)}`;
Inclusion (recommended):
{
posts: { title: true, content: true },
authors: { name: true }
}
Exclusion:
{
posts: { internalNotes: false, draft: false }
}
Control the depth of relationship population in enriched results to optimize performance and control data fetching.
Add depth parameters to your search query to specify how deeply relationships should be populated for each collection:
import qs from 'qs-esm';
// Set different depths for different collections
const depthConfig = {
posts: 3, // Populate posts to depth 3
authors: 1, // Populate authors to depth 1
categories: 2 // Populate categories to depth 2
};
const params = {
query: 'javascript',
enrichResults: true,
depth: depthConfig
};
const url = `/search?${qs.stringify(params)}`;
// URL EXAMPLE
/search?query=javascript&enrichResults=true&depth[posts]=3&depth[authors]=1&depth[categories]=2
You can combine depth control with field selection for maximum optimization:
const params = {
query: 'javascript',
enrichResults: true,
select: {
posts: { title: true, content: true, author: true },
authors: { name: true, bio: true }
},
depth: {
posts: 2, // Populate author relationship
authors: 0 // Don't populate further relationships in authors
}
};
Default Behavior
Transform complex field types into searchable formats before indexing in Algolia.
type FieldTransformer = (
value: unknown,
fieldConfig: Field,
collectionSlug: string
) => string | number | boolean | string[] | null;
// Collection with group field
const Posts: CollectionConfig = {
slug: 'posts',
fields: [
{
name: 'authorDetails',
type: 'group',
fields: [
{ name: 'name', type: 'text' },
{ name: 'title', type: 'text' },
{ name: 'bio', type: 'textarea' },
],
},
],
};
// Plugin configuration
algoliaSearchPlugin({
// ... other config
collections: [
{
slug: 'posts',
indexFields: ['title', 'authorDetails'], // Include the group field
},
],
fieldTransformers: {
group: (value, fieldConfig, collectionSlug) => {
if (fieldConfig.name === 'authorDetails' && value) {
const { name, title, bio } = value as any;
return [name, title, bio].filter(Boolean).join(' ');
}
return null; // Don't index other group fields
},
},
});
The plugin includes default transformers for:
text: Just return the value as-is, or in case of an array, join the elements with a commarichText: Converts rich text to plain textrelationship: Extracts related document titles or names (returns value.title, value.name, value.slug, or String(value.id))upload: Indexes file names and metadata (uses value?.filename, value?.alt, value?.title, or null)select: Handles select field valuesarray: Joins array elements into a comma-separated string; if elements are objects, their values are concatenated into a single string before joining.Control who can trigger re-indexing operations.
By default, any authenticated user can trigger re-indexing:
const defaultAccess = ( req: PayloadRequest ) => !!req.user;
Restrict access to specific user roles:
algoliaSearchPlugin({
// ... other config
reindexAccess: ( req ) => {
return req.user?.role === 'admin' || req.user?.role === 'editor';
},
});
Hide the re-index button while keeping the endpoint active:
algoliaSearchPlugin({
// ... other config
hideReindexButton: true,
});
import { algoliaSearchPlugin } from '@veiag/payload-algolia-search';
export default buildConfig({
collections: [Posts, Authors, Categories],
plugins: [
algoliaSearchPlugin({
credentials: {
appId: process.env.ALGOLIA_APP_ID!,
apiKey: process.env.ALGOLIA_API_KEY!,
indexName: process.env.ALGOLIA_INDEX_NAME!,
},
collections: [
{
slug: 'posts',
indexFields: ['title', 'excerpt', 'content', 'tags'],
},
{
slug: 'authors',
indexFields: ['name', 'bio'],
},
],
}),
],
});
algoliaSearchPlugin({
credentials: {
appId: process.env.ALGOLIA_APP_ID!,
apiKey: process.env.ALGOLIA_API_KEY!,
indexName: process.env.ALGOLIA_INDEX_NAME!,
},
collections: [
{
slug: 'products',
indexFields: ['title', 'description', 'category', 'brand', 'sku','specifications'],
},
],
fieldTransformers: {
group: (value, fieldConfig) => {
if (fieldConfig.name === 'specifications' && value) {
return Object.entries(value)
.map(([key, val]) => `${key}: ${val}`)
.join(' ');
}
return null;
},
},
});
// React search component example with depth control
const SearchResults = ({ query }) => {
const [results, setResults] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const searchProducts = async () => {
if (!query) return;
setLoading(true);
try {
const params = {
query,
enrichResults: true,
hitsPerPage: 20,
depth: {
products: 2, // Include category and brand relationships
categories: 1 // Don't over-fetch category relationships
},
select: {
products: { title: true, description: true, price: true, category: true, brand: true },
categories: { name: true, slug: true }
}
};
const response = await fetch(`/search?${qs.stringify(params)}`);
const data = await response.json();
setResults(data);
} catch (error) {
console.error('Search failed:', error);
} finally {
setLoading(false);
}
};
searchProducts();
}, [query]);
if (loading) return <div>Searching...</div>;
if (!results) return null;
return (
<div>
<p>{results.nbHits} results found</p>
{results.hits.map((hit) => {
const enrichedData = results.enrichedHits[hit.objectID];
return (
<div key={hit.objectID}>
<h3 dangerouslySetInnerHTML={{
__html: hit._highlightResult.title.value
}} />
{enrichedData && (
<>
<p>{enrichedData.description}</p>
{enrichedData.category && (
<span>Category: {enrichedData.category.name}</span>
)}
</>
)}
</div>
);
})}
</div>
);
};
Symptoms: Documents aren't appearing in Algolia after creation/updates.
Solutions:
indexFields includes existing fieldsSymptoms: Search requests fail with 404 errors.
Solutions:
searchEndpoint is not set to falseSymptoms: No re-index button in the admin panel.
Solutions:
hideReindexButton is not set to trueSymptoms: enrichedHits is empty even with enrichResults=true.
Solutions:
Current Limitation: This plugin does not currently support Payload's localization features. Localized fields will not be indexed correctly.
Workarounds:
For collections with many documents:
hitsPerPageWe welcome contributions!
search
Generate fast-to-query search records from your Payload documents
10083.1k/wk45.4kv3
algolia search
Sync Payload collections to Algolia with draft-aware indexing, admin reindexing and headless React search hooks
540/wk15v3
payload typesense
Typesense search for Payload with automatic document sync, search endpoints and a React search component
83235/wk32v3
payload plugin algolia
Sync Payload collections to an Algolia search index
480/wk17v3
payload meilisearch
Sync Payload collections to Meilisearch indexes on create, update and delete
320/wk39v1
payload related items
Related posts for Payload using BM25 and Jaccard similarity scoring, with no external AI service
410/wk3v3