Search

This is the official documentation of Forest Admin Cloud.

To improve your admin panel's search functionality, consider overriding the default search system with more efficient technologies. Options like PostgreSQL's tsquery and tsvector, Algolia, or Elasticsearch provide advanced search capabilities and faster query responses. These tools can significantly improve your app's search results and performance.

The following sections assume that you have correctly followed all the steps in the Code Customization Getting Started guide.


To make the code easier to read, all the code snippets below should be wrapped in the following code. Ensure you update the collection name as needed.

import type { Agent } from '@forestadmin/forest-cloud';
import { Schema } from '../typings';

export default function customizeAgent(agent: Agent<Schema>) {
  agent.customizeCollection('users', collection =>
    // Insert the code snippet here.
  );
}

Extending the default search engine

To modify your collection's default search behavior, use the replaceSearch() function. This function provides access to the default search logic through the context argument.

collection.replaceSearch((searchString, extendedMode, context) => {
  return context.generateSearchFilter(searchString, {
    extended: extendedMode;
    excludeFields: ['gender'];
    includeFields: ['address:street'];
  })
});

Arguments:

  • excludedFields* [String]: Remove the fields to the default search engine.

  • extended* Boolean: When activated, extended search also includes direct relationships.

  • includeFields* [String]: Add the fields to the default search engine.

The generateSearchFilter() function returns an object containing the set of rules for searching. You can adjust these rules to better fit your needs or to make your searches more efficient.

collection.replaceSearch((searchString, extendedMode, context) => {
  const defaultCondition = context.generateSearchFilter(searchString, {
    extended: extendedMode;
    excludeFields: ['gender'];
    includeFields: ['address:street'];
  });
  
  return {
    aggregator: 'Or',
    conditions: [
      defaultCondition,
      { field: 'address:street', operator: 'IContains', value: searchString },
    ]
  };
});

Examples

Enabling case-sensitivity

By default, searches are case insensitive. To enable case-sensitive searches, switch from the IContains operator to the Contains operator.

collection.replaceSearch((searchString, extendedMode, context) => {
  const defaultCondition = context.generateSearchFilter(searchString);
  
  return {
    aggregator: 'Or',
    conditions: [
      defaultCondition,
      { field: 'first_name', operator: 'Contains', value: searchString },
      { field: 'last_name', operator: 'Contains', value: searchString },
    ]
  };
});

Searching on specific columns based on user input format

To improve the default search functionality, this example below differentiates between a product reference (e.g., bcdaefCFABDEdfEF) and a barcode value (e.g., 9876543210) using regex. This enables the search to target the appropriate field based on the input type.

const productReferenceRegexp = /^[a-f]{16}$/i;
const barCodeRegexp = /^[0-9]{10}$/i;

collection.replaceSearch(async (searchString, extendedMode, context) => {
  if (productReferenceRegexp.test(searchString)) {
    return context.generateSearchFilter(searchString, {
      onlyFields: ['reference'],
    });
  } else if (barCode.test(searchString)) {
    return context.generateSearchFilter(searchString, {
      onlyFields: ['barCode'],
    });
  } else {
    return context.generateSearchFilter(searchString);
  }
});

Using an external API

When your data is stored and organized in a cloud-based service, another storage system, or a service specifically for searching through text, you can opt to use these instead of the default search engine.

const algoliasearch = require('algoliasearch');
const client = algoliasearch('APPLICATION_ID', 'WRITE_API_KEY');
const index = client.initIndex('indexName');

collection.replaceSearch(async (searchString, extendedMode, context) => {
  const { hits } = await index.search(searchString, {
    attributesToRetrieve: ['id'],
    hitsPerPage: 50,
  });

  return { field: 'id', operator: 'In', value: hits.map(h => h.id) };
});

Last updated