Drupal: Search and Search API
|
This content was generated with the assistance of AI and should be verified against the official Drupal documentation before being relied on in production. This sub-section’s Drupal bibliography lists the reference material consulted while preparing these pages. |
Search is the feature visitors reach for when navigation fails: a school group looking for "telescope" on the museum site, an editor looking for an exhibition by accession number, or an AI agent that needs to find collection objects. Drupal solves it in three layers. The core Search module is a small keyword search over content and users that has existed since the earliest versions and leaves core in Drupal 12. The contributed Search API module is the framework that almost every serious Drupal site uses instead: it indexes any entity into a search backend (the Drupal database, Apache Solr, Elasticsearch or OpenSearch) and exposes the index to Views, blocks and the Facets module. On top of that, AI Search adds semantic search with vector databases.
This page was written against Drupal 11.4.x, with Drush 13 or 14 and PHP 8.3 or later, and the facts were checked against the
official documentation on 2026-10-30. The running example is the Meridian Museum: an index meridian_content over the
exhibition, event and news content types and the custom collection_object entity of the module meridian_collection,
a search page at /search, facets for the topic and gallery vocabularies, and the kiosk and AI-agent use cases that
need the same index. Drupal 12.0.0 is scheduled for the week of 2026-12-07 and the end of life of Drupal 10 for 2026-12-09,
so both are described as upcoming.
Who uses what
-
Site builders decide which fields are searchable, build the search page as a view and place facet blocks.
-
Administrators choose and operate the backend, schedule indexing and keep the search engine out of public reach.
-
Developers alter queries, add processors, index custom entities and expose search to other systems.
-
Editors only notice search when new content does not show up, which is almost always an indexing question.
Core Search
The core search module provides a search page configuration entity, a plugin type for search sources, a
search_index table that holds word-to-item scores, and the /search pages. Two plugins ship with it: Content
(node_search) and Users (user_search). A site can have several search pages, each bound to one plugin, configured at
admin/config/search/pages. The page "Content" is the default and is reachable at /search/node?keys=telescope.
Indexing is incremental and runs from cron. The setting "Number of items to index per cron run" (default 100) on the same screen limits the work per run, so a large site needs several cron runs before the indexing percentage reaches 100. The page also exposes the minimum word length and CJK tokenisation settings. Core Search has no notion of external engines, facets or relevance tuning beyond the built-in ranking factors (keyword relevance, recently posted, number of comments).
# Core Search (in core up to Drupal 11.x): Drush has no core Search commands.
# Click "Re-index site" at /admin/config/search/pages, then let cron build the index.
ddev drush cron
Core Search needs the permissions "Use search" (search content), "Use advanced search" and "Administer search". Grant the
first one to the anonymous role only on sites that really publish content to everyone; the plugin respects node access, so
unpublished content stays hidden from users without access.
Core Search leaves core in Drupal 12
The change record for Drupal 12 states that the Search module is removed from core in the 12.0.x line, was deprecated in
11.5.0, and moves to the contributed project drupal/search. That project has a stable 1.0.0 release (2026-09-17) for
Drupal ^11.5 and ^12, so a site that must keep the core behaviour can install it with
composer require 'drupal/search:^1.0' before upgrading. The user guide and older books still describe core Search as a
permanent part of Drupal; since 12 it is an optional contributed module. The module is one of nine that Drupal 12 moves to
contrib (see removed core modules and replacements and
preparing for Drupal 12). The rule from those pages applies: install the
contributed version before the upgrade and do not uninstall the module first.
The recommended path for new sites is not to rebuild a site around core Search at all but to start with Search API and its database backend, which needs no external service and is the closest equivalent.
Search API
Search API (drupal/search_api, current stable release 8.x-1.43 on 2026-10-30, compatible with Drupal ^10.3, ^11 and ^12) is
a framework, not a search engine. It separates four concerns:
| Concept | What it is |
|---|---|
Server |
A configured connection to a backend: its backend plugin and the connection details. Stored as |
Index |
The set of data sources, fields and processors that define what is searchable. An index belongs to one server. Stored as
|
Datasource |
A source of items, most often an entity type with bundle and language selection ( |
Processor |
A step that alters items or queries: entity status, content access, HTML filter, tokenizer, stopwords, aggregated fields, highlighting and more. |
Items travel from datasources through the tracker (which records what is indexed and what is stale), through the processors, into the backend. Queries travel the opposite way: a Views or custom query is built against the index, processors alter it, the backend runs it and returns result items that Views renders.
Installing Search API with DDEV
The Meridian project requires the framework, the database backend module comes with it, and Facets and Autocomplete are separate projects.
ddev composer require 'drupal/search_api:^1.43' 'drupal/facets:^3.0' 'drupal/search_api_autocomplete:^1.12'
ddev drush en search_api search_api_db search_api_db_defaults facets search_api_autocomplete -y
ddev drush cr
The extension search_api_db_defaults creates a ready-made content index and a search view on a fresh site. Because the Standard profile no longer
creates the Article and Page content types (since 11.4), tutorials that assume them must create content types first; a
Meridian-style site builds its own index as follows.
The database backend
The Database Search backend (search_api_db) stores the index in extra tables of the Drupal database, one set per index.
It supports full-text matching, optional partial matching, and the processors listed below. The project page recommends it
mostly for testing and smaller sites, and that is the honest scale limit: the database is shared with the rest of the
site, ranking is simple, and a large index competes with page requests for database capacity. For the Meridian
catalogue (tens of thousands of objects) it is perfectly adequate and has no extra infrastructure to run.
Steps in the interface:
-
Add a server at
admin/config/search/search-api/add-server, choose "Database" and keep the defaults (minimum word length 3, and "Search on parts of a string" if partial matching is needed). -
Add an index at
admin/config/search/search-api/add-index, choose the datasources and bundles, select the server and tick "Index items immediately" if changes should appear without waiting for cron. Set the "Cron batch size" (default 50; 0 disables cron indexing). -
On the Fields tab add the fields: Fulltext for titles and body text, String or Integer for filters and sorts, and a Boolean for
on_display. Use the property path to reach fields on referenced entities, for example the term name offield_topic. -
On the Processors tab enable at least Entity status (keeps unpublished content out), Content access (applies node access), Ignore case, Tokenizer, HTML filter and Highlight.
-
Index from the View tab, or from Drush.
The two machine-name-bound configuration objects are ordinary configuration and travel with configuration management (see configuration management). An abbreviated export of the server and of the index looks like this:
# config/sync/search_api.server.meridian_db.yml
langcode: en
status: true
dependencies:
module:
- search_api_db
id: meridian_db
name: 'Meridian database'
description: ''
backend: search_api_db
backend_config:
database: 'default:default'
min_chars: 3
matching: words
autocomplete:
suggest_suffix: true
suggest_words: true
# config/sync/search_api.index.meridian_content.yml (abbreviated)
langcode: en
status: true
id: meridian_content
name: 'Meridian content'
server: meridian_db
datasource_settings:
'entity:node':
bundles:
default: false
selected:
- event
- exhibition
- news
languages:
default: true
selected: { }
'entity:collection_object':
bundles:
default: true
selected: { }
languages:
default: true
selected: { }
processor_settings:
entity_status: { }
content_access: { }
ignorecase:
all_fields: true
weights:
preprocess_index: -20
preprocess_query: -20
tracker_settings:
default:
indexing_order: fifo
options:
index_directly: true
cron_limit: 50
Source: Search API: Getting started
The exact keys in an export depend on the Search API version and on the chosen processors, so create the objects in the interface and export them rather than writing the YAML by hand.
Other backends
| Backend | Project and status (2026-10-30) | When it fits |
|---|---|---|
Apache Solr |
|
Large indexes, ranking control, spell checking, multilingual analysers, hosted Solr offerings. |
Elasticsearch |
|
Teams that already operate Elasticsearch; full-text and vector search through the same index. |
OpenSearch |
|
Amazon OpenSearch Service or a self-hosted OpenSearch cluster. |
All three are contributed. None ships in core and none is part of Search API itself, so the version of the backend module and the version of the search engine must both be checked before an upgrade, in addition to Search API.
Solr in practice
Search API Solr needs a Solr server whose config set matches the Drupal index. The module generates that configuration from the fields, languages and processors of the index; download it from the server page ("Get config.zip") and install it in Solr, or let a Solr Cloud setup receive it directly. Because the generated schema follows the Drupal fields, any change of the index fields, or any module update that changes the schema, means regenerating and uploading the config set and then reindexing. A forgotten upload is the most common reason for Solr errors after a deployment. For a local Meridian site, run Solr in its own container and point the Solr connector at it.
# .ddev/docker-compose.solr.yaml (sketch; see the DDEV Solr add-on for a maintained version)
services:
solr:
image: solr:9.8
container_name: ddev-${DDEV_SITENAME}-solr
command: solr-precreate meridian /opt/solr/server/solr/configsets/meridian
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: ${DDEV_APPROOT}
volumes:
- ./solr/conf:/opt/solr/server/solr/configsets/meridian/conf
expose:
- "8983"
Security rules for any external backend: keep the port off the public internet, use HTTP basic authentication or HTTPS (hosted
Solr providers require one of them), keep credentials in settings.php from the environment and not in exported
configuration, and treat the engine as a second place where unpublished or private content could leak. If an index
contains content that a role may not see, the Content access processor and the Entity status processor must be enabled,
otherwise the backend will happily return it.
Elasticsearch and OpenSearch
Both connectors talk to the cluster through the vendor’s PHP client. The Elasticsearch connector requires Search API and
supports full-text and vector search; for OpenSearch use search_api_opensearch, because the Elasticsearch connector does not
support OpenSearch. Check the supported server version on the project page, as engines reach end of life on their own
schedule.
Indexing
Indexing is where most search tickets start, so the mechanics matter.
-
Tracker: the default tracker keeps a row for every item with a "changed" marker. Saving an entity marks it stale.
-
Index items immediately: if enabled, the changed items are sent to the backend at the end of the request that saved them.
-
Cron: if the cron batch size is not 0, each cron run indexes that many stale items. Cron can be started by the system scheduler or
drush core:cron(see cron, queues and maintenance). -
Drush: bulk operations that would time out in the browser.
-
Reindex marks every item stale without deleting indexed data, so search keeps working while the index rebuilds. Clear deletes the indexed data. Rebuild tracker recreates the tracking information and is needed after the datasources change on a site that already has content.
# Index everything stale for the Meridian index, 200 items per batch
ddev drush search-api:index meridian_content --batch-size=200
# Mark everything stale after changing processors or fields (search keeps working)
ddev drush search-api:reset-tracker meridian_content
# Recreate tracking information after changing datasources
ddev drush search-api:rebuild-tracker meridian_content
# Delete the indexed data (search returns nothing until indexed again)
ddev drush search-api:clear meridian_content
# Status of all indexes (indexed items, total, percentage)
ddev drush search-api:status
Source: Elasticsearch Connector guide: Indexing your content (the Drush commands are the same for every backend)
The drush deploy sequence does not reindex. After a deployment that changes index configuration, add search-api:reset-tracker
(and search-api:index) to the deployment script (see deployment and CI/CD and
Drush).
Indexing a custom entity
Search API indexes any content entity that exposes the Entity API’s property information, so the Meridian
collection_object entity (see custom entity types) appears as a datasource
without code. Fields defined as base fields or configurable fields are available by property path. Keep these points in mind:
-
Reference fields are followed through the property path (
field_gallery:entity:name), which is how a facet on the gallery name is built without duplicating the label. -
Computed values and rendered output can be indexed with the Rendered item processor, which renders the item in a chosen view mode and indexes the text. Re-render when a template changes, by resetting the tracker.
-
Translations are separate items. Select the languages on the datasource, and add the Language field to filter by it.
Altering queries with an OOP hook
Search API invokes hook_search_api_query_alter() before a query runs. Since Drupal 11.1 the hook is a method on an autowired
class (see hooks and OOP hooks). The example keeps visitors' searches to objects that
are on display, while staff with the permission still see everything.
<?php
declare(strict_types=1);
namespace Drupal\meridian_collection\Hook;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\search_api\Query\QueryInterface;
/**
* Restricts the public collection search.
*/
final class SearchHooks {
public function __construct(
private readonly AccountProxyInterface $currentUser,
) {}
/**
* Implements hook_search_api_query_alter().
*/
#[Hook('search_api_query_alter')]
public function queryAlter(QueryInterface $query): void {
if ($query->getIndex()->id() !== 'meridian_content') {
return;
}
if (!$this->currentUser->hasPermission('administer collection objects')) {
// Items of other entity types have no on_display value; keep them.
$group = $query->createConditionGroup('OR');
$group->addCondition('on_display', TRUE);
$group->addCondition('search_api_datasource', 'entity:collection_object', '<>');
$query->addConditionGroup($group);
}
}
}
The same class must be registered nowhere: with autowiring, core finds classes in src/Hook and wires the constructor.
Remember that the query’s cache metadata must vary by permission when this query feeds a cached block or view; Search API views
declare the user context for this reason.
Search views
A search page is a view whose base table is the index (the base table is named "Index Meridian content" in the Views UI). The view then offers everything described in Views: displays, exposed filters, sorts and pagers, with these additions:
-
The Fulltext search exposed filter collects the keywords and searches either all fulltext fields or the selected ones.
-
The Search: Relevance sort orders by the backend’s score. Search API fields can be added as sort criteria.
-
The result rows can be the Rendered entity (the item rendered in a view mode, loaded from the database) or Fields from the index itself, which avoids loading entities and is much faster on large result sets.
-
The Views integration is part of the main module. The Search views submodule adds access checks for entities in search views, which matters whenever result rows are rendered from entities.
The Meridian site’s /search view uses a fulltext filter, a sort by relevance, 12 results per page, the search_result
view mode for entities, and the facet blocks described next. It is configuration, exported like any view.
# config/sync/views.view.meridian_search.yml (fragment)
id: meridian_search
label: 'Meridian search'
base_table: search_api_index_meridian_content
display:
page_1:
display_plugin: page
display_options:
path: search
exposed_block: false
default:
display_options:
filters:
search_api_fulltext:
id: search_api_fulltext
table: search_api_index_meridian_content
field: search_api_fulltext
plugin_id: search_api_fulltext
exposed: true
expose:
identifier: keys
label: 'Search the museum'
sorts:
search_api_relevance:
id: search_api_relevance
table: search_api_index_meridian_content
field: search_api_relevance
plugin_id: search_api
order: DESC
pager:
type: mini
options:
items_per_page: 12
Because this view’s exposed filter is submitted by GET, the results page is addressed as /search?keys=telescope, and every
visitor query creates a distinct URL that a crawler can follow.
robots.txt and the search page
Drupal 11.4 adds Disallow: /search? and Disallow: /index.php/search? to the scaffold robots.txt, next to the older
Disallow: /search/ rule. The old rule covers core Search’s path-style pages but not a query-string search page such as
the Views-based /search?keys=.... A site that has its own, customised robots.txt (a file that Composer scaffolding does not
overwrite) must add the rule itself; otherwise crawlers explore an endless set of search result URLs and create load on the
search backend.
# robots.txt (Meridian excerpt)
User-agent: *
Disallow: /search/
Disallow: /search?
Disallow: /index.php/search?
Source: Drupal core: scaffold robots.txt
robots.txt is advice to well-behaved crawlers, not access control. Pair it with caching of search results and, where it
matters, rate limiting at the proxy.
Facets
Facets (drupal/facets, 3.0.7 on 2026-10-30 for Drupal 10.1 or later and 11) turns index fields into filters that narrow the
result list: "Topic: Astronomy (14)", "Gallery: Space Hall (6)". A facet is configuration at admin/config/search/facets
with these parts:
-
a facet source (a Search API view display, for example
search_api:views_page__meridian_search__page_1); -
a field of the index (
topic,gallery,on_display, or a date); -
a widget (links, checkboxes, dropdown, range slider, date);
-
processors (count display, sort by count or label, hide when only one value, URL processor).
The facet is rendered as a block or, in Facets 3, as a facets exposed filter inside the view, which the project page recommends for new Facets and Views setups. Facets 3 works only with Search API and its AJAX support is limited to the exposed filters; Facets 2 also supported core Search and keeps the older AJAX approach with no upgrade path. Facets are also available for core Search in Facets 2 only, which is a reason to move away from core Search before Drupal 12.
ddev composer require 'drupal/facets:^3.0'
ddev drush en facets facets_summary -y
ddev drush cex -y
Source: Drupal.org: Facets project page
Common pitfalls: a facet field must be indexed as a String or Integer type (not Fulltext), otherwise the values are tokenised words; the facet block is empty until the view has run, so place it in a region that is shown on the results page; and the counts reflect the current query, so cached blocks must be cached per URL query arguments.
Autocomplete and "did you mean"
The Search API Autocomplete module (8.x-1.12, Drupal ^10.2 or ^11) adds suggestions to the keyword field: searches already
made, suggestions from the backend, or items matching the prefix. Each search (a view) gets an autocomplete configuration
at admin/config/search/search-api/index/meridian_content/autocomplete. Autocomplete requests are frequent, so use the
backend’s suggester, set a minimum length of 3 characters and consider page caching exclusions only where required.
Access control and security
-
Entity status and Content access are processors, not defaults on every index: enable them. Indexing a node’s rendered text with Rendered item as the anonymous user uses the access of that user, which is the safest default.
-
Private fields should not be indexed in a shared index; use a second index with its own server, and restrict the view to the right permission.
-
A query’s text is untrusted. Use the fulltext filter instead of concatenating the string into a Solr or SQL query, and rely on Search API’s query objects (
$query->keys()andaddCondition()). -
Highlighting (the Highlight processor) returns excerpts built from content; the HTML filter processor prevents tags from leaking into the index.
-
Do not expose the Solr, Elasticsearch or OpenSearch HTTP port. The engines have administrative APIs.
AI Search and vector databases
Keyword search finds the words a visitor typed; semantic search finds the meaning. The AI Search project
(drupal/ai_search, 2.0.0-alpha3 and 1.3.0-alpha6 on 2026-10-30; the project page recommends the 1.x release for production
until a stable 2.x exists) is a Search API backend that stores embeddings, numeric vectors produced by an AI provider, in a
vector database. A query is turned into an embedding and the nearest vectors are returned, so "instrument for looking at
planets" can find an object titled "Refracting telescope, 1887". AI Search was a submodule of the AI module and is now its
own project; it depends on the AI module (drupal/ai, 1.5.2) and on Search API. The same index feeds retrieval-augmented
generation: a chatbot or an agent receives the closest content as context.
The vector database is chosen by a provider module. The AI Search project page lists Milvus, Pinecone, Postgres, MariaDB, OpenSearch, Azure AI Search, SQLite, Qdrant and Weaviate among them, and notes that the module needs a vector database whichever one is used. Provider modules have their own status; some have no stable release yet, so check each project page before relying on one. SQLite and Postgres need no extra service for a small site; Milvus and Pinecone are for larger ones. Search API Solr does not yet offer native dense-vector support, whereas the Elasticsearch connector advertises vector search.
# AI Search with the Postgres provider; the AI provider key comes from the Key module, never from exported configuration
ddev composer require 'drupal/ai:^1.5' 'drupal/ai_search:^1.3@alpha' 'drupal/key:^1.20'
ddev drush en ai ai_search -y
# Set the provider key through an environment variable referenced by a Key entity
ddev config --web-environment-add="MERIDIAN_AI_API_KEY=CHANGE_ME"
ddev restart
In the Meridian build the index meridian_semantic uses the AI Search backend, a text chunker splits long descriptions,
and the embeddings are created by the configured provider. Costs and privacy follow the provider: object descriptions leave
the site to be embedded unless a local model is used. The full setup of providers, the Key module and the agents is covered
in the AI module and providers and in
MCP Server and AI agents, where the same index is exposed to an agent as a
search tool.
Which search should a site use?
| Need | Choice | Reason |
|---|---|---|
Tiny brochure site, Drupal 11 only |
Core Search (contrib |
No extra modules, but no facets in Facets 3 and no future in core. |
Typical content site |
Search API with the database backend, plus Facets |
No infrastructure; same configuration as larger setups. |
Large index, advanced ranking, multilingual analysis |
Search API Solr, or Elasticsearch or OpenSearch connectors |
The engine does the heavy lifting; operate it securely. |
Natural-language questions, chatbots |
AI Search with a vector database, usually next to a keyword index |
Semantic matching and retrieval for agents. |
A headless kiosk can use the same indexes through a view with a REST export display or through JSON:API (see JSON:API).
Where older books and tutorials are outdated
-
Drupal 10 Masterclass (2023) presents Search API, Solr and Facets as the standard approach and is right in spirit, but its screens and versions predate Search API 1.43, Facets 3 (exposed filters, Search API only), Search API Solr 4.x for Solr 7 to 9 and the removal of Solr versions below 7 from the main line. It treats core Search as part of Drupal; since Drupal 12 it is contributed.
-
Drupal 10 Development Cookbook (2023) does not cover Search API at all, and tutorials built on it that use only core Search must move to the contributed
searchproject or to Search API before Drupal 12. -
Tutorials that say "set the indexing throttle in the Search settings" refer to core Search. Search API uses the per-index cron batch size and the Drush commands above.
-
Older Facets articles describe facet blocks with AJAX for every source and core Search support. Both are Facets 2 behaviour.
-
Books and posts from before 2025 never mention AI Search or vector databases.
-
Guides that advise adding
Disallow: /search/only are incomplete for query-string search pages: addDisallow: /search?(part of the scaffold file from 11.4).
Common errors
| Symptom | Cause and fix |
|---|---|
New content never appears |
Indexing is only through cron and cron is not running, or the cron batch size is 0 with "Index items immediately" off. Run
|
Everything disappears after a config import |
The index was changed (fields or processors), which marks items stale. Reindex. |
Unpublished or private content appears in results |
Entity status and Content access processors are not enabled, or the result rows are rendered from the index without access checks. |
Solr errors after a deployment |
The generated config set no longer matches the index. Download or upload the config set again, then reindex. |
Facet shows words instead of values |
The field is indexed as Fulltext. Index it as String (and the entity reference as its id or label). |
|
The |
Core |
It was uninstalled or not replaced. Install the contributed |