Strapi plugin for AACSearch - Integrate search capabilities into your headless CMS.
- Real-Time Indexing: Automatic content synchronization with AACSearch
- Multi-Language Support: Index content in multiple locales
- Custom Content Types: Support for any Strapi content type
- Advanced Relations: Index relational data and populate fields
- Webhook Integration: Real-time updates via Strapi lifecycle hooks
- Admin Panel: Manage search settings and view analytics
- Draft Preview: Index and search draft content (optional)
# npm
npm install @aacsearch/strapi-plugin
# yarn
yarn add @aacsearch/strapi-plugin
# pnpm
pnpm add @aacsearch/strapi-plugin// config/plugins.js
module.exports = {
'aacsearch': {
enabled: true,
config: {
apiKey: env('AACSEARCH_API_KEY'),
organizationId: env('AACSEARCH_ORG_ID'),
baseUrl: env('AACSEARCH_BASE_URL', 'https://api.aacsearch.com'),
indexName: env('AACSEARCH_INDEX_NAME', 'strapi-content'),
// Content types to index
contentTypes: {
'api::article.article': {
enabled: true,
fields: ['title', 'content', 'excerpt'],
populate: ['author', 'category'],
publicationState: 'published' // 'published' | 'preview' | 'both'
},
'api::page.page': {
enabled: true,
fields: ['title', 'body', 'seo.metaDescription'],
populate: ['sections.image']
}
},
// Indexing options
indexing: {
batchSize: 100,
delayBetweenBatches: 1000,
includeUnpublished: false,
includeDrafts: false
},
// Webhook settings
webhooks: {
enabled: true,
events: ['create', 'update', 'delete', 'publish', 'unpublish']
}
}
}
};# .env
AACSEARCH_API_KEY=your_api_key_here
AACSEARCH_ORG_ID=your_organization_id
AACSEARCH_INDEX_NAME=my-strapi-site// Content type configuration
contentTypes: {
'api::product.product': {
enabled: true,
fields: [
'name',
'description',
'price',
'sku'
],
populate: [
'category',
'images',
'variants.color'
],
transformData: (entry) => {
// Custom data transformation
return {
...entry,
searchableText: `${entry.name} ${entry.description}`,
priceRange: getPriceRange(entry.price)
};
}
}
}contentTypes: {
'api::article.article': {
enabled: true,
fieldMapping: {
// Map Strapi fields to search index fields
'title': 'title',
'content': 'body',
'publishedAt': 'published_date',
'author.name': 'author_name',
'category.name': 'category'
},
filters: {
// Only index published articles
publishedAt: { $notNull: true },
// Exclude private articles
private: { $eq: false }
}
}
}contentTypes: {
'api::article.article': {
enabled: true,
localized: true,
localeStrategy: 'separate', // 'separate' | 'combined'
fields: ['title', 'content'],
populate: ['localizations']
}
}// services/search.js
module.exports = ({ strapi }) => ({
async indexContent(contentType, id) {
const entry = await strapi.entityService.findOne(contentType, id, {
populate: '*'
});
return await strapi.plugin('aacsearch').service('indexing').indexEntry(
contentType,
entry
);
},
async searchContent(query, options = {}) {
return await strapi.plugin('aacsearch').service('search').search(
query,
options
);
}
});// api/search/controllers/search.js
module.exports = {
async search(ctx) {
const { query, filters, limit = 20, offset = 0 } = ctx.query;
try {
const results = await strapi.plugin('aacsearch').service('search').search({
query,
filters,
limit: parseInt(limit),
offset: parseInt(offset)
});
ctx.body = results;
} catch (error) {
ctx.throw(500, 'Search failed', { error });
}
},
async reindex(ctx) {
const { contentType } = ctx.params;
try {
await strapi.plugin('aacsearch').service('indexing').reindexContentType(contentType);
ctx.body = { message: 'Reindexing started' };
} catch (error) {
ctx.throw(500, 'Reindexing failed', { error });
}
}
};// Next.js example
export async function getStaticProps() {
const searchResults = await fetch(`${process.env.STRAPI_URL}/api/search?query=react`);
const results = await searchResults.json();
return {
props: { results }
};
}
// React component
function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleSearch = async () => {
const response = await fetch(`/api/search?query=${encodeURIComponent(query)}`);
const data = await response.json();
setResults(data.documents || []);
};
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
/>
<button onClick={handleSearch}>Search</button>
{results.map(result => (
<div key={result.id}>
<h3>{result.title}</h3>
<p>{result.excerpt}</p>
</div>
))}
</div>
);
}The plugin automatically handles content changes through Strapi lifecycle hooks:
// Automatically handled by the plugin
// - afterCreate: Index new entries
// - afterUpdate: Update search index
// - afterDelete: Remove from index
// - afterPublish: Index published content
// - afterUnpublish: Remove unpublished content// config/functions/bootstrap.js
module.exports = async () => {
// Custom indexing logic
strapi.db.lifecycles.subscribe({
models: ['api::article.article'],
async afterUpdate(event) {
const { model, result } = event;
// Custom logic before indexing
if (result.featured) {
await strapi.plugin('aacsearch').service('indexing').prioritizeEntry(
model.uid,
result.id
);
}
}
});
};Navigate to Settings โ AACSearch:
- Configure API credentials
- Enable/disable content types
- Set field mappings
- View indexing status
- Trigger manual re-indexing
View search performance in AACSearch โ Analytics:
- Popular search queries
- Search volume trends
- Zero-result searches
- Click-through rates
The plugin adds search capabilities to the Content Manager:
- Quick search across all content types
- Filter by content type
- Search within specific fields
- Advanced search with multiple criteria
# Strapi CLI commands
npm run strapi aacsearch:reindex # Reindex all content
npm run strapi aacsearch:reindex --type=api::article.article # Reindex specific type
npm run strapi aacsearch:clear # Clear search index
npm run strapi aacsearch:status # Show indexing status
npm run strapi aacsearch:sync # Sync configuration// api/search/routes/search.js
module.exports = {
routes: [
{
method: 'GET',
path: '/search',
handler: 'search.search',
config: {
auth: false, // or configure authentication
rateLimit: {
max: 100,
duration: 60000
}
}
},
{
method: 'POST',
path: '/search/reindex/:contentType',
handler: 'search.reindex',
config: {
policies: ['admin::isAuthenticatedAdmin']
}
}
]
};// Custom result transformation
contentTypes: {
'api::product.product': {
enabled: true,
fields: ['name', 'description'],
transformResult: (hit, originalEntry) => {
return {
...hit,
url: `/products/${originalEntry.slug}`,
thumbnail: originalEntry.images?.[0]?.url,
inStock: originalEntry.inventory > 0
};
}
}
}// Configure facets for filtering
contentTypes: {
'api::article.article': {
enabled: true,
facets: [
'category.name',
'author.name',
'tags.name'
],
numericFacets: [
'publishedAt',
'readTime'
]
}
}MIT ยฉ AACSearch