> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joinrefine.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Text Search

> Search products using natural language queries with visual similarity matching

## Overview

Text search combines semantic text understanding with visual similarity to find relevant products. Use the `visualWeight` parameter to balance between text matching and visual similarity.

## Basic Usage

```typescript theme={null}
import { Refine } from '@refine-ai/sdk';

const refine = new Refine({
  apiKey: process.env.REFINE_API_KEY,
  organizationId: 'org_abc123',
  catalogId: 'cat_xyz789'
});

const results = await refine.search.text({
  query: 'red running shoes',
  topK: 20
});

results.results.forEach(product => {
  console.log(`${product.title} - $${product.price}`);
});
```

## Parameters

<ParamField body="query" type="string" required>
  The search query. Natural language queries like "summer dress for wedding" work best.
</ParamField>

<ParamField body="topK" type="number" required>
  Number of results to return. Recommended: 12-48 for grid layouts.
</ParamField>

<ParamField body="visualWeight" type="number" default="0.3">
  Balance between text and visual search (0.0 to 1.0).

  * `0.0` = Pure text matching
  * `1.0` = Pure visual similarity
  * `0.3` = Text-primary with visual boost (default)
</ParamField>

<ParamField body="filters" type="Filter[]">
  Array of filters to apply. See [Filters](/sdk/search/filters) for operators.
</ParamField>

<ParamField body="sortBy" type="SortBy">
  Sorting configuration with `field` and `order` ('ascending' | 'descending').
</ParamField>

## With Filters and Sorting

```typescript theme={null}
const results = await refine.search.text({
  query: 'summer dress',
  topK: 24,
  visualWeight: 0.5,
  filters: [
    { field: 'price', operator: 'gte', value: 50 },
    { field: 'price', operator: 'lte', value: 200 },
    { field: 'metadata.color', operator: 'in', value: ['red', 'blue', 'green'] },
    { field: 'stock', operator: 'gt', value: 0 }
  ],
  sortBy: {
    field: 'price',
    order: 'ascending'
  }
});
```

## Response

```typescript theme={null}
interface SearchResponse {
  results: SearchResultItem[];
  filterOptions?: FilterOption[];
  serveId: string;
  totalResults: number;
}

interface SearchResultItem {
  productId: string;
  title: string;
  price: number;
  imageUrl: string;
  metadata: Record<string, any>;
  score: number;
}
```

<ResponseField name="results" type="SearchResultItem[]">
  Array of matching products, ordered by relevance.
</ResponseField>

<ResponseField name="filterOptions" type="FilterOption[]">
  Available filter values based on the result set. Useful for building faceted navigation.
</ResponseField>

<ResponseField name="serveId" type="string">
  Unique identifier for this search serve. Used internally for event tracking.
</ResponseField>

<ResponseField name="totalResults" type="number">
  Total number of matching products (before `topK` limit). Use for pagination UI.
</ResponseField>

## Tracking Search Results

Always track search results to power analytics and improve recommendations:

```typescript theme={null}
const results = await refine.search.text({ query: 'shoes', topK: 20 });

const searchContext = refine.events.trackSearch(
  'shoes',
  results.results,
  { 
    surface: 'search_results',
    totalResults: results.totalResults 
  }
);

// When user clicks a product
searchContext.trackClick(productId, position);

// When a product becomes visible
searchContext.trackView(productId, position);

// When user adds to cart
searchContext.trackAddToCart(productId);
```

See [Event Tracking](/sdk/events/overview) for more details.

## Visual Weight Recommendations

The optimal `visualWeight` depends on your product category:

| Category        | Recommended Weight | Reason                       |
| --------------- | ------------------ | ---------------------------- |
| Fashion/Apparel | 0.5 - 0.7          | Visual appearance is primary |
| Electronics     | 0.2 - 0.4          | Specs and text matter more   |
| Home Goods      | 0.4 - 0.6          | Balanced                     |
| Art/Decor       | 0.7 - 0.9          | Almost purely visual         |
| Books           | 0.1 - 0.2          | Text is primary              |
| Jewelry         | 0.6 - 0.8          | Visual details matter        |

<Tip>
  A/B test different visual weights to find the optimal balance for your specific catalog and user behavior.
</Tip>

## Pagination

Use `topK` with offset for pagination:

```typescript theme={null}
const PAGE_SIZE = 24;
let currentPage = 0;

async function loadPage(page: number) {
  const results = await refine.search.text({
    query: 'shoes',
    topK: PAGE_SIZE,
    // Note: offset is handled server-side with pagination tokens
  });
  
  currentPage = page;
  return results;
}
```

<Note>
  For best performance, prefer "Load More" patterns over traditional pagination. Each new search request is independent and provides fresh relevance scoring.
</Note>

## Error Handling

```typescript theme={null}
import { 
  RefineValidationError, 
  RefineAuthError,
  RefineRateLimitError 
} from '@refine-ai/sdk';

try {
  const results = await refine.search.text({ query: 'shoes', topK: 20 });
} catch (error) {
  if (error instanceof RefineValidationError) {
    console.error('Invalid search parameters:', error.message);
  } else if (error instanceof RefineAuthError) {
    console.error('Check your API key');
  } else if (error instanceof RefineRateLimitError) {
    console.error('Rate limited, retry after:', error.retryAfter);
  }
}
```

## Complete Example

```typescript theme={null}
import { Refine, RefineValidationError } from '@refine-ai/sdk';

const refine = new Refine({
  apiKey: process.env.REFINE_API_KEY,
  organizationId: 'org_abc123',
  catalogId: 'cat_xyz789'
});

async function performSearch(query: string, filters: Filter[] = []) {
  try {
    const results = await refine.search.text({
      query,
      topK: 24,
      visualWeight: 0.5,
      filters,
      sortBy: { field: 'relevance', order: 'descending' }
    });

    // Track the search
    const searchContext = refine.events.trackSearch(query, results.results, {
      surface: 'search_results',
      totalResults: results.totalResults
    });

    return {
      products: results.results,
      total: results.totalResults,
      filterOptions: results.filterOptions,
      trackClick: (productId: string, position: number) => {
        searchContext.trackClick(productId, position);
      }
    };
  } catch (error) {
    if (error instanceof RefineValidationError) {
      return { products: [], total: 0, error: 'Invalid search' };
    }
    throw error;
  }
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Image Search" icon="image" href="/sdk/search/image-search">
    Search using uploaded images
  </Card>

  <Card title="Filters" icon="filter" href="/sdk/search/filters">
    Filter operators reference
  </Card>
</CardGroup>
