Today we’ll learn how to promote results by pinning given documents at the top of search results. This can be used in ecommerce to give more visibility to featured products or in-app search to prioritize user-specific content. We’ll use Node.js and React to implement promoted documents.

Need promoted documents inside the Meilisearch engine? Give your feedback!

This tutorial will use a sample movie dataset. Our objective will be to have “Finding Nemo” at the top of the results when typing “funny”, “children”, or “fish”.

Folder structure

In this guide, we’ll separate the frontend code from the backend code used to configure Meilisearch.

We’ll organize our project using the following folder structure:

├─ node_modules/
├─ data/ <-- Backend code goes there
│  ├─ setup.js
│  ├─ movies.json
│  ├─ promoted-movies.json
├─ src/ <-- React code will go there
│  ├─ App.jsx
│  ├─ main.js
│  ├─ ...
├─ package.json

First, create a directory for our application (eg. my-app) or use an existing one. In our application directory, let’s create a data folder for the backend code.

Download the JSON dataset and store them in our new data folder. Download the datasets here:

Alright. Let’s code!

Meilisearch setup

First, we need to launch a Meilisearch instance. You can do so by following instructions in the local installation guide or creating an account on Meilisearch Cloud. We’ll write a setup script to configure our Meilisearch instance and seed the data.

This setup script uses the JavaScript SDK, but you can use any of Meilisearch SDKs.

In this tutorial, we're using Node 18. Let's install the JavaScript SDK:

npm install meilisearch

Then, let's create a setup.js file:

// data/setup.js

import { MeiliSearch } from 'meilisearch'

// Load the datasets
import movies from './movies.json' assert { type: 'json' }
import promotedMovies from './promoted_movies.json' assert { type: 'json' }

// Load credentials from environment
const credentials = {
  host: 'your Meilisearch host',
  apiKey: 'your Meilisearch Admin API key' 

// Configuration
const INDEX_NAME = 'movies'
const PROMOTED_INDEX_NAME = `promoted-${INDEX_NAME}`

const searchableAttributes = ['title', 'overview', 'genre', 'release_date']
const displayedAttributes = ['id', 'title', 'overview', 'genre', 'poster', 'release_date']

const setup = async () => {
	console.log('🚀 Seeding your Meilisearch instance')
	const client = new MeiliSearch(credentials)
	// Adding searchable attributes to movies index
	// In the promoted movies index, only `keywords` is searchable
	// Both indexes have the same visible attributes
	// `keywords` is hidden in the promoted movies index
	// Adding documents
	await client.index(INDEX_NAME).addDocuments(movies)
	await client.index(PROMOTED_INDEX_NAME).addDocuments(promotedMovies)
	// Wait for tasks completion
	await watchTasks(client, INDEX_NAME)
	await watchTasks(client, PROMOTED_INDEX_NAME)

// Helper to watch tasks
const watchTasks = (client, uid) {
	console.log(`Watching tasks on index ${uid}`)
	const tasks = await client.index(uid).getTasks()
	console.log(`${uid} index: waiting for tasks`)
	await client.index(uid).waitForTasks(tasks)
	console.log(`${uid} index: tasks finished`)

await setup()

This script creates our two indexes. Let’s recap what it does:

  • It sets the same displayed attributes for both movies and promoted-movies.
  • It sets the searchable attributes; only keywords is searchable in the promoted-movies index.
  • It adds documents to our Meilisearch indexes.
To optimize indexing speed, always add documents after configuring settings.

We can now run the script with Node.js:

node setup.js

Our Meilisearch instance is now configured and seeded. 🥳 It’s time to implement promoted results using React!

Displaying promoted results

First, we’ll navigate back to the root directory of our application (eg. my-app). We’ll use Vite to create the React app in this folder.

npm create vite@latest . --template react

If the CLI asks to remove existing files from the current directory, answer 'No'.

Then, let’s install the additional dependencies for integrating InstantSearch with Meilisearch:

npm install @meilisearch/instant-meilisearch react-instantsearch-hooks-web

Let’s edit our App component to display search results. We want the promoted results to show up before the rest. Let’s replace the App.jsx boilerplate code with our own:

// src/App.jsx

import { instantMeiliSearch } from '@meilisearch/instant-meilisearch'
import { InstantSearch, SearchBox, Hits, Index } from 'react-instantsearch-hooks-web';
const MEILI_HOST = 'your Meilisearch host'
const MEILI_SEARCH_KEY = 'your Meilisearch Search API key'

const meilisearchClient = instantMeiliSearch(MEILI_HOST, MEILI_SEARCH_KEY)

const App = () => (
		<h1>Hello React</h1>
		<InstantSearch searchClient={meilisearchClient}>
			<SearchBox />
			<Index indexName='promoted-movies'>
				<Hits />
			<h2>More results</h2>
			<Index indexName='movies'>
				<Hits />

export default App;

We can now run the development server using npm run dev. In the browser, our app should display something like this:

HTML output of the code above
Displaying promoted results before other results

Congrats. We’ve successfully displayed our promoted results before the rest of the results. 🎉

Got stuck? Don’t hesitate to ask for help in our Discord community.

Going further

This tutorial explored one approach for implementing promoted results. An alternative technique would be implementing document pinning in the backend—using one of Meilisearch SDKs. This approach has the benefit of allowing to merge results in a single response (as if they were coming from a single index).

Both techniques can achieve similar results. We also plan to integrate promoted documents in the Meilisearch engine. Give your feedback on the previous link to help us prioritize it.

For more things Meilisearch, you can subscribe to our newsletter. You can learn more about our product by checking out the roadmap and participating in our product discussions.

For anything else, join our developers' community on Discord.

I’ll see you there.