Skip to main content
The transformItems prop allows you to post-process search results before they’re displayed. This is useful for normalizing data, adding computed properties, filtering results, or adapting non-standard record structures.

Basic Usage

The transformItems function receives an array of hits and must return an array of hits:
import { DocSearch } from '@docsearch/react';

<DocSearch
  appId="YOUR_APP_ID"
  apiKey="YOUR_SEARCH_API_KEY"
  indexName="documentation"
  transformItems={(items) => {
    return items.map((item) => ({
      ...item,
      // Add or modify properties
    }));
  }}
/>

Function Signature

transformItems?: (items: DocSearchHit[]) => DocSearchHit[];
The function receives hits from all queries (if using multiple indices) and should return an array of transformed hits.

Common Use Cases

1. Adding Computed Properties

Add custom properties to hits for use in custom components:
<DocSearch
  appId="YOUR_APP_ID"
  apiKey="YOUR_SEARCH_API_KEY"
  indexName="documentation"
  transformItems={(items) => {
    return items.map((item) => {
      const publishDate = new Date(item.published_at);
      const isNew = Date.now() - publishDate.getTime() < 7 * 24 * 60 * 60 * 1000;
      
      return {
        ...item,
        isNew,
        formattedDate: publishDate.toLocaleDateString(),
      };
    });
  }}
/>

2. Filtering Results

Remove certain results based on custom logic:
<DocSearch
  appId="YOUR_APP_ID"
  apiKey="YOUR_SEARCH_API_KEY"
  indexName="documentation"
  transformItems={(items) => {
    // Only show results from the current version
    const currentVersion = 'v2';
    return items.filter((item) => 
      item.version === currentVersion || !item.version
    );
  }}
/>

3. Modifying URLs

Adjust result URLs to match your routing:
<DocSearch
  appId="YOUR_APP_ID"
  apiKey="YOUR_SEARCH_API_KEY"
  indexName="documentation"
  transformItems={(items) => {
    return items.map((item) => ({
      ...item,
      url: item.url.replace('https://old-domain.com', ''),
      url_without_anchor: item.url_without_anchor.replace('https://old-domain.com', ''),
    }));
  }}
/>

4. Adding Index Badges

Identify which index each result came from:
<DocSearch
  appId="YOUR_APP_ID"
  apiKey="YOUR_SEARCH_API_KEY"
  indices={['docs', 'blog', 'api']}
  transformItems={(items) => {
    return items.map((item) => {
      const indexName = item.__autocomplete_indexName || 'unknown';
      const indexLabels = {
        docs: 'πŸ“š Docs',
        blog: '✍️ Blog',
        api: 'πŸ”§ API',
      };
      
      return {
        ...item,
        hierarchy: {
          ...item.hierarchy,
          lvl0: `${indexLabels[indexName] || indexName} β€’ ${item.hierarchy.lvl0}`,
        },
      };
    });
  }}
/>

5. Sorting Results

Re-sort results based on custom criteria:
<DocSearch
  appId="YOUR_APP_ID"
  apiKey="YOUR_SEARCH_API_KEY"
  indexName="documentation"
  transformItems={(items) => {
    return items.sort((a, b) => {
      // Prioritize lvl1 results over content
      if (a.type === 'lvl1' && b.type !== 'lvl1') return -1;
      if (a.type !== 'lvl1' && b.type === 'lvl1') return 1;
      
      // Then sort by custom priority field
      return (b.priority || 0) - (a.priority || 0);
    });
  }}
/>

Adapting Non-Standard Record Structures

If your Algolia index doesn’t follow the DocSearch schema, use transformItems to map your data structure:
<DocSearch
  appId="YOUR_APP_ID"
  apiKey="YOUR_SEARCH_API_KEY"
  indexName="custom_content"
  searchParameters={{
    attributesToRetrieve: ['*'],
    attributesToSnippet: ['*:15'],
  }}
  transformItems={(items) => {
    return items.map((item) => ({
      objectID: item.objectID,
      content: item.body || item.description || '',
      url: item.domain + item.path,
      url_without_anchor: item.domain + item.path,
      type: 'content',
      anchor: null,
      hierarchy: {
        lvl0: (item.breadcrumb || []).join(' β€Ί ') || item.category || '',
        lvl1: item.title || item.h1 || '',
        lvl2: item.subtitle || item.h2 || '',
        lvl3: item.h3 || null,
        lvl4: null,
        lvl5: null,
        lvl6: null,
      },
      _highlightResult: item._highlightResult,
      _snippetResult: item._snippetResult,
    }));
  }}
/>
When transforming records to match the DocSearch structure, ensure you include all required fields: objectID, content, url, url_without_anchor, type, anchor, hierarchy, _highlightResult, and _snippetResult.

Required DocSearchHit Structure

type DocSearchHit = {
  objectID: string;
  content: string | null;
  url: string;
  url_without_anchor: string;
  type: 'askAI' | 'content' | 'lvl0' | 'lvl1' | 'lvl2' | 'lvl3' | 'lvl4' | 'lvl5' | 'lvl6';
  anchor: string | null;
  hierarchy: {
    lvl0: string;
    lvl1: string;
    lvl2: string | null;
    lvl3: string | null;
    lvl4: string | null;
    lvl5: string | null;
    lvl6: string | null;
  };
  _highlightResult: DocSearchHitHighlightResult;
  _snippetResult: DocSearchHitSnippetResult;
  // ... additional optional fields
};

Working with Highlight and Snippet Results

_highlightResult and _snippetResult contain HTML-formatted text with search term matches wrapped in highlight tags:
<DocSearch
  appId="YOUR_APP_ID"
  apiKey="YOUR_SEARCH_API_KEY"
  indexName="documentation"
  transformItems={(items) => {
    return items.map((item) => {
      // Access highlighted content
      const highlightedTitle = item._highlightResult?.hierarchy?.lvl1?.value || item.hierarchy.lvl1;
      const snippetContent = item._snippetResult?.content?.value || item.content;
      
      return {
        ...item,
        // You can use these in custom hit components
        highlightedTitle,
        snippetContent,
      };
    });
  }}
/>

Combining with Other Features

With Multiple Indices

transformItems processes hits from all indices together:
<DocSearch
  appId="YOUR_APP_ID"
  apiKey="YOUR_SEARCH_API_KEY"
  indices={[
    { name: 'docs_v1', searchParameters: { facetFilters: ['version:v1'] } },
    { name: 'docs_v2', searchParameters: { facetFilters: ['version:v2'] } },
  ]}
  transformItems={(items) => {
    // All hits from both indices
    return items.map((item) => {
      const indexName = item.__autocomplete_indexName;
      const version = indexName.includes('v2') ? 'v2' : 'v1';
      
      return {
        ...item,
        versionBadge: version,
      };
    });
  }}
/>

With Custom Hit Component

Pass transformed data to your custom component:
function CustomHit({ hit }) {
  return (
    <a href={hit.url}>
      <h4>{hit.hierarchy.lvl1}</h4>
      {hit.isNew && <span className="badge">New</span>}
      {hit.formattedDate && <time>{hit.formattedDate}</time>}
    </a>
  );
}

<DocSearch
  appId="YOUR_APP_ID"
  apiKey="YOUR_SEARCH_API_KEY"
  indexName="documentation"
  transformItems={(items) => {
    return items.map((item) => ({
      ...item,
      isNew: Date.now() - new Date(item.date).getTime() < 7 * 24 * 60 * 60 * 1000,
      formattedDate: new Date(item.date).toLocaleDateString(),
    }));
  }}
  hitComponent={CustomHit}
/>

Performance Considerations

transformItems runs on every search query. Avoid expensive operations like API calls or complex computations that could slow down the search experience.

Good Practices

// βœ… Fast transformations
transformItems={(items) => {
  return items.map((item) => ({
    ...item,
    displayUrl: item.url.replace(/^https?:\/\//, ''),
  }));
}

// βœ… Simple filtering
transformItems={(items) => 
  items.filter((item) => item.status === 'published')
}

// βœ… Basic sorting
transformItems={(items) => 
  items.sort((a, b) => (b.priority || 0) - (a.priority || 0))
}

Avoid

// ❌ API calls
transformItems={async (items) => {
  // Don't do this!
  return Promise.all(
    items.map(async (item) => {
      const data = await fetch(`/api/enrich/${item.objectID}`);
      return { ...item, ...data };
    })
  );
}}

// ❌ Heavy computations
transformItems={(items) => {
  return items.map((item) => {
    // Don't run expensive algorithms here
    const score = complexScoringAlgorithm(item);
    return { ...item, score };
  });
}}

Debugging Tips

Console Logging

Add console.log(items) in your transform function to inspect the raw hits and understand the data structure.
transformItems={(items) => {
  console.log('Raw items:', items);
  const transformed = items.map(/* ... */);
  console.log('Transformed items:', transformed);
  return transformed;
}

Type Checking

Use TypeScript to ensure your transformed items match the expected structure.
import type { DocSearchHit } from '@docsearch/react';

transformItems: (items: DocSearchHit[]): DocSearchHit[] => {
  return items.map((item) => ({
    ...item,
    // TypeScript will validate this structure
  }));
}

Complete Example

import { DocSearch } from '@docsearch/react';
import '@docsearch/css';

function App() {
  return (
    <DocSearch
      appId="YOUR_APP_ID"
      apiKey="YOUR_SEARCH_API_KEY"
      indices={[
        { name: 'documentation' },
        { name: 'blog_posts' },
      ]}
      transformItems={(items) => {
        return items
          // Filter out draft content
          .filter((item) => item.status !== 'draft')
          // Add computed properties
          .map((item) => {
            const indexName = item.__autocomplete_indexName || '';
            const isDoc = indexName.includes('documentation');
            const isBlog = indexName.includes('blog');
            
            return {
              ...item,
              // Add source badge
              hierarchy: {
                ...item.hierarchy,
                lvl0: `${isDoc ? 'πŸ“š' : isBlog ? '✍️' : 'πŸ“„'} ${item.hierarchy.lvl0}`,
              },
              // Normalize URLs
              url: item.url.replace(/^https?:\/\/[^/]+/, ''),
              url_without_anchor: item.url_without_anchor.replace(/^https?:\/\/[^/]+/, ''),
              // Add metadata
              sourceType: isDoc ? 'documentation' : isBlog ? 'blog' : 'other',
            };
          })
          // Sort by relevance
          .sort((a, b) => {
            // Prioritize documentation over blog
            if (a.sourceType === 'documentation' && b.sourceType !== 'documentation') return -1;
            if (a.sourceType !== 'documentation' && b.sourceType === 'documentation') return 1;
            return 0;
          });
      }}
      maxResultsPerGroup={10}
    />
  );
}

Build docs developers (and LLMs) love