> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/algolia/docsearch/llms.txt
> Use this file to discover all available pages before exploring further.

# Transforming Search Results

> Use transformItems to modify, filter, or adapt search results before rendering

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:

```jsx theme={null}
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

```typescript theme={null}
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:

```jsx theme={null}
<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:

```jsx theme={null}
<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:

```jsx theme={null}
<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:

```jsx theme={null}
<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:

```jsx theme={null}
<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:

```jsx theme={null}
<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,
    }));
  }}
/>
```

<Note>
  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`.
</Note>

## Required DocSearchHit Structure

```typescript theme={null}
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:

```jsx theme={null}
<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:

```jsx theme={null}
<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:

```jsx theme={null}
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

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

### Good Practices

```jsx theme={null}
// ✅ 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

```jsx theme={null}
// ❌ 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

<Card title="Console Logging" icon="bug">
  Add `console.log(items)` in your transform function to inspect the raw hits and understand the data structure.
</Card>

```jsx theme={null}
transformItems={(items) => {
  console.log('Raw items:', items);
  const transformed = items.map(/* ... */);
  console.log('Transformed items:', transformed);
  return transformed;
}
```

<Card title="Type Checking" icon="check">
  Use TypeScript to ensure your transformed items match the expected structure.
</Card>

```typescript theme={null}
import type { DocSearchHit } from '@docsearch/react';

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

## Complete Example

```jsx theme={null}
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}
    />
  );
}
```
