Skip to main content

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_data to keep indexes across restarts.

Building the index

Extract text from your site and send it to Meilisearch as documents.

Typical workflow

  1. Render the static site and collect the generated HTML files.
  2. Use a script to parse headings, content, and URLs.
  3. Send the records to the documents endpoint.
  4. Configure searchable fields and ranking rules as needed.

Search plugin integration

The meilisearch-docsearchopen in new window 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 host and apiKey values 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

  1. Download or containerize Meilisearch and start the service.
  2. Secure the API with a master key and optional read key.
  3. Extract content from the static site and push it as documents.
  4. Configure ranking rules and searchable fields.
  5. Add the client side plugin to the site and customize its style.
  6. Reindex after each documentation update.
  7. 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_KEY is 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 pdfminer or PyPDF2 to 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:

  1. Stop and remove the container:
docker stop meilisearch
docker rm meilisearch
  1. Remove the data directory: The docker run command 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_data directory is owned by root by default, so removing it may require sudo. 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 the docker run command.

7. Correcting the Volume Location

If you created the meili_data directory in the wrong location on your host machine:

  1. Stop the running Meilisearch container:
docker stop meilisearch
  1. Move the data directory to the correct location:
sudo mv /path/to/wrong/meili_data /path/to/correct/location/
  1. Restart the container, making sure to update the path in the -v flag 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.

  1. Create a docker-compose.yml file:
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
  1. Create a .env file 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
  1. Start the service in the background:
docker-compose up -d
  1. 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:

  1. Add the search container In your layout or a VuePress component (e.g., .vuepress/components/SearchBox.vue), include:

    <div id="docsearch"></div>
    
  2. Initialize docsearch in 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.

  1. 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.
  2. Customizing CSS Override the default styles after importing CSS:

    .md-docsearch__wrapper {
      max-width: 600px;
    }
    

Back to top
Back to step-by-step