Fulltext Search with Meilisearch
Plugin configuration details
The meilisearch-docsearch client library can be integrated and configured in your VuePress site (or any static HTML page) as follows.
1. Add the search container
Place a container element in your layout where the search box should appear, for example in a VuePress component (.vuepress/components/SearchBox.vue) or directly in a layout file:
<div id="docsearch"></div>
2. Initialize docsearch in a client script
Create or edit .vuepress/client.ts to import and configure the search:
import { defineClientConfig } from 'vuepress/client';
import { docsearch } from 'meilisearch-docsearch';
import 'meilisearch-docsearch/css';
export default defineClientConfig({
onMounted() {
docsearch({
container: '#docsearch', // Selector for the search container
host: 'http://localhost:7700', // Your Meilisearch server URL
apiKey: 'SEARCH_ONLY_API_KEY', // A search-only API key
indexUid: 'docs', // The UID of your index
placeholder: 'Search the docs...', // Input placeholder text
// Optional: modify items before rendering
transformItems(items) {
return items.filter(item => !item.tags?.includes('internal'));
},
// Optional: additional search parameters
searchParameters: {
facetFilters: ['category:guide'],
},
});
},
});
3. Available options
container(string): CSS selector for the search box element.host(string): Full URL of your Meilisearch instance (include port).apiKey(string): Search-only API key (never use the master key in the client).indexUid(string): Name of the index to query.placeholder(string): Placeholder text inside the search input.transformItems(function): Hook to modify search results before they render.searchParameters(object): Meilisearch query options (filters, facets, etc.).
4. Customizing CSS
The default styles are imported from meilisearch-docsearch/css. To override:
/* Adjust wrapper width */
.md-docsearch__wrapper {
max-width: 600px;
}
Back to top
Back to step-by-step
- search
- icdp-internal tags:
- meilisearch
- fulltext-search ai_note: This article was generated with AI assistance. summary: Host Meilisearch, index a static site, add a search plugin, and keep it up to date. post_date: 2025-08-13
Fulltext Search with Meilisearch
ICDP Internal
(TO DO: post this under the ICDP Internal category, or move to internal Wiki)
Problem description
This mDIS-documentation site includes a search feature based on the default VuePress search, which indexes only title/headers of html documents.
Static sites like this one often ship without a backend capable of effectively indexing their content. A solution is to rely on an external provider or third party search engine. However, these can be slow, incomplete, or depend on commercial services. A self hosted search engine solves this gap.
Meilisearch overview
Meilisearch is a small, fast full text engine. It accepts documents over an API and provides instant search results. The service can run directly on the host or inside a separate container. For production it is recommended to secure the API with keys and to persist the data directory. See below.
Running the service
- Download the latest binary or run the official Docker image.
- Set the master key and expose port
7700. - Mount a volume for
/meili_datato keep indexes across restarts.
Building the index
Extract text from your site and send it to Meilisearch as documents.
Typical workflow
- Render the static site and collect the generated HTML files.
- Use a script to parse headings, content, and URLs.
- Send the records to the
documentsendpoint. - Configure searchable fields and ranking rules as needed.
Search plugin integration
The meilisearch-docsearch plugin can power client-side search. It queries the index and displays results while the user types. See Plugin configuration details below for full setup.
Customizing the plugin
- Adjust
hostandapiKeyvalues to point to your service. - Tweak the CSS to match your theme.
- Use hooks to filter or sort results before they render.
Maintenance tasks
- Monitor logs for failed index updates.
- Rebuild the index after significant content changes.
- Back up the data directory and master key.
- Upgrade the Meilisearch binary when new releases appear.
After documentation updates
When the site is rebuilt, rerun the indexing script. If the schema changed, drop the existing index before sending new data. Restart Meilisearch when the binary is upgraded.
To-do list
- Download or containerize Meilisearch and start the service.
- Secure the API with a master key and optional read key.
- Extract content from the static site and push it as documents.
- Configure ranking rules and searchable fields.
- Add the client side plugin to the site and customize its style.
- Reindex after each documentation update.
- Back up data and update Meilisearch when new versions are released.
Step-by-step: Running, Securing, and Populating Meilisearch
1. Run Meilisearch in Docker
The easiest way to run Meilisearch is via Docker:
docker run -it --rm \
--name meilisearch \
-e MEILI_MASTER_KEY='your_master_key_here' \
-p 7700:7700 \
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:latest
- Replace
'your_master_key_here'with a strong, 16+ character key. - The data will persist in
./meili_data. - The API will be available at
http://localhost:7700.
API Keys
- The
MEILI_MASTER_KEYis required for all admin operations. - You can generate search-only keys via the API:
curl -X POST 'http://localhost:7700/keys' \ -H 'Authorization: Bearer your_master_key_here' \ -H 'Content-Type: application/json' \ --data '{"expiresAt": null, "description":"Search key","actions":["search"],"indexes":["docs"]}' - Replace
"docs"with your desired index name.
2. Create and Populate an Index
Create an index
curl -X POST 'http://localhost:7700/indexes' \
-H 'Authorization: Bearer your_master_key_here' \
-H 'Content-Type: application/json' \
--data '{"uid":"docs"}'
Populate the index with documents
Suppose you have a script that extracts content from your static site (HTML or Markdown). Here’s a minimal example for HTML files:
import glob, requests, bs4
API_URL = 'http://localhost:7700/indexes/docs/documents'
API_KEY = 'your_master_key_here'
for fname in glob.glob('public/**/*.html', recursive=True):
with open(fname) as f:
soup = bs4.BeautifulSoup(f, 'html.parser')
doc = {
'id': fname,
'title': soup.title.string if soup.title else '',
'content': soup.get_text(),
'url': fname.replace('public/', '/'),
}
requests.post(API_URL, json=[doc], headers={'Authorization': f'Bearer {API_KEY}'})
Should you index .md or .html files?
- Index HTML files: These are what users see and what search should return. Markdown files are for authoring, not for search.
3. Omitting or Including Files
- To omit files (e.g.,
.svg,.pdf), filter them out in your script:if fname.endswith('.svg') or fname.endswith('.pdf'): continue - To explicitly include certain files, use a whitelist or pattern match in your script.
- External docs (PDFs, etc.): Meilisearch can index metadata or extracted text, but not binary files directly. For PDFs, use a tool like
pdfminerorPyPDF2to extract text, then send that as a document.
4. Example: Indexing PDFs
import PyPDF2
fname = 'docs/manual.pdf'
with open(fname, 'rb') as f:
reader = PyPDF2.PdfReader(f)
text = '\n'.join(page.extract_text() for page in reader.pages)
doc = {'id': fname, 'title': 'Manual', 'content': text, 'url': '/docs/manual.pdf'}
requests.post(API_URL, json=[doc], headers={'Authorization': f'Bearer {API_KEY}'})
5. Customizing Searchable Fields and Ranking
You can set which fields are searchable and how results are ranked:
curl -X PATCH 'http://localhost:7700/indexes/docs/settings/searchable-attributes' \
-H 'Authorization: Bearer your_master_key_here' \
-H 'Content-Type: application/json' \
--data '["title", "content"]'
6. Cleanup: Removing the Container and Volume
To completely remove the Meilisearch container and its data:
- Stop and remove the container:
docker stop meilisearch
docker rm meilisearch
- Remove the data directory: The
docker runcommand above uses a bind mount, which maps a host directory (./meili_data) into the container. To delete the data, simply remove that directory.
# Warning: This permanently deletes your search index data.
sudo rm -rf ./meili_data
Note: The
./meili_datadirectory is owned byrootby default, so removing it may requiresudo. To avoid this, you can:
- Change ownership:
sudo chown -R $(id -u):$(id -g) ./meili_data- Run the container as your user: add
--user $(id -u):$(id -g)to thedocker runcommand.
7. Correcting the Volume Location
If you created the meili_data directory in the wrong location on your host machine:
- Stop the running Meilisearch container:
docker stop meilisearch
- Move the data directory to the correct location:
sudo mv /path/to/wrong/meili_data /path/to/correct/location/
- Restart the container, making sure to update the path in the
-vflag to point to the new, correct location:
docker run -it --rm \
--name meilisearch \
-e MEILI_MASTER_KEY='your_master_key_here' \
-p 7700:7700 \
-v /path/to/correct/location/meili_data:/meili_data \
getmeili/meilisearch:latest
8. Using Docker Compose for Easier Management
For a more declarative and manageable setup, use docker-compose.
- Create a
docker-compose.ymlfile:
version: '3.8'
services:
meilisearch:
image: getmeili/meilisearch:latest
container_name: meilisearch
ports:
- "7700:7700"
volumes:
- ./meili_data:/meili_data
environment:
- MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
restart: unless-stopped
- Create a
.envfile in the same directory to store your master key securely:
# .env
# Use a strong, randomly generated key
MEILI_MASTER_KEY=your_super_secret_and_long_master_key
- Start the service in the background:
docker-compose up -d
- To stop the service and remove the container:
docker-compose down
(This does not remove the ./meili_data volume by default).
Plugin GUI configuration details
The meilisearch-docsearch client library can be integrated and configured in your VuePress site (or any static HTML page) as follows:
Add the search container In your layout or a VuePress component (e.g.,
.vuepress/components/SearchBox.vue), include:<div id="docsearch"></div>Initialize
docsearchin a client script Edit or create.vuepress/client.ts:import { defineClientConfig } from 'vuepress/client'; import { docsearch } from 'meilisearch-docsearch'; import 'meilisearch-docsearch/css'; export default defineClientConfig({ onMounted() { docsearch({ container: '#docsearch', // CSS selector for the search box host: 'http://localhost:7700', // Meilisearch server URL apiKey: 'SEARCH_ONLY_API_KEY', // Use a search-only key indexUid: 'docs', // Name of the index placeholder: 'Search the docs...', // Optional hooks: transformItems(items) { return items.filter(item => !item.tags?.includes('internal')); }, searchParameters: { facetFilters: ['category:guide'], }, }); }, });
In VuePress 2, the client.ts isn’t run on the server or in CI. Rather, it’s a client-side bootstrap file. Here’s what that means:
Who is “the client”? The client is the browser that ultimately loads your generated site.
When is it used?
At build time (e.g. in GitLab CI/CD), VuePress’s bundler (Vite or Webpack) picks up client.ts, compiles it into your site’s JavaScript bundle, and emits it alongside your HTML/CSS. At runtime, once a user opens any page in their browser, that compiled bundle runs. Your defineClientConfig({ onMounted() { … } }) hook fires in the browser, attaches the search box, and invokes docsearch().
- How does it work in CI/CD?
In your GitLab pipeline you run npx vuepress build (or an npm script). That build step processes client.ts just like any other source file: transpiling, tree-shaking, bundling. After the build finishes, you deploy the static output (dist/ or public) to your web server. The pipeline never “runs” client.ts itself; it merely packages it into the final JS served to end-users. In short, client.ts is the entry point for client-only logic. It lives in your repo, is bundled at build time, and executes in the browser at runtime.
Available options
container(string): Selector for the search input container.host(string): Full URL to the Meilisearch instance.apiKey(string): Search-only API key (do not expose master key).indexUid(string): UID of the index to query.placeholder(string): Placeholder text in the input.transformItems(function): Hook to modify result items.searchParameters(object): Additional Meilisearch query parameters.
Customizing CSS Override the default styles after importing CSS:
.md-docsearch__wrapper { max-width: 600px; }