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

# Search API

> Complete reference for useSearch / useInstantSearch / useSearchConfig, the standalone search fetchers, the SearchApp methods, and search attribution events

# Search API

Reference for the Glood Hydrogen **Search v1** surface. For a guided walkthrough, see
[Search & Discovery](/for-developers/glood-hydrogen-sdk/search).

<Note>
  All search calls are **v1** on the search host (`https://search.glood.ai`) and authenticate with
  `Authorization: Bearer <apiKey>` + `x-shop`. Params are camelCase (mapped to snake\_case on the
  wire); responses are snake\_case exactly as returned by the backend.
</Note>

## Hooks

React hooks, each usable within a `<GloodProvider>` whose client has the search app
registered (`.use(search())`).

### useSearch()

Runs a full product search for the current params and exposes attribution helpers.

```typescript theme={null}
function useSearch(
  params: SearchParams,
  options?: { skip?: boolean }
): UseSearchResult
```

**Parameters** — `params` is a [`SearchParams`](/for-developers/glood-hydrogen-sdk/api-reference/types#searchparams) (all optional; `query` may be `""`). `options.skip` (default `false`) holds fetching until required params are ready.

**Returns — `UseSearchResult`**

| Attribute          | Type                           | Description                                                                                |
| ------------------ | ------------------------------ | ------------------------------------------------------------------------------------------ |
| `products`         | `SearchItem[]`                 | Result products (or `{ product_id }` refs when `view: 'product_ids'`). Empty while loading |
| `data`             | `SearchResponse \| null`       | Full response (`request_id`, `pagination`, `facets`, `metadata`)                           |
| `loading`          | `boolean`                      | `true` while in flight                                                                     |
| `error`            | `Error \| null`                | `GloodApiError` on API failures                                                            |
| `refetch`          | `() => void`                   | Re-run with the same params                                                                |
| `trackSearch`      | `(track: SearchTrack) => void` | Emit `glood:instant_search_triggered`                                                      |
| `trackResultView`  | `(track: SearchTrack) => void` | Emit `glood:search_result_rendered`                                                        |
| `trackResultClick` | `(track: SearchTrack) => void` | Emit `glood:search_result_clicked`                                                         |
| `trackAddToCart`   | `(track: SearchTrack) => void` | Emit `glood:search:add_to_cart`                                                            |
| `trackFilter`      | `(track: SearchTrack) => void` | Emit `glood:search_filter_updated`                                                         |

```tsx theme={null}
const { products, data, trackResultClick } = useSearch({
  query: 'running shoe',
  sort: 'price_asc',
  facets: [{ key: 'color', operator: 'or', value: ['red'] }],
  pagination: { page: 1, limit: 24 },
  options: { includeFacets: true, personalize: true },
});
```

### useInstantSearch()

Type-ahead search. On an empty / too-short query the response carries `trending`.

```typescript theme={null}
function useInstantSearch(
  params: InstantSearchParams,
  options?: { skip?: boolean }
): UseInstantSearchResult
```

**Parameters** — `params` is an [`InstantSearchParams`](/for-developers/glood-hydrogen-sdk/api-reference/types#instantsearchparams) (`query` required, may be `""`). Use `options.skip` to gate on the minimum query length.

**Returns — `UseInstantSearchResult`**

| Attribute     | Type                            | Description                                                  |
| ------------- | ------------------------------- | ------------------------------------------------------------ |
| `data`        | `InstantSearchResponse \| null` | Full response (products, suggestions, autocorrect, trending) |
| `products`    | `SearchItem[]`                  | Convenience: `data.products.results`                         |
| `suggestions` | `SearchSuggestion[]`            | Convenience: keyword suggestions                             |
| `collections` | `InstantSearchCollection[]`     | Convenience: collection results                              |
| `pages`       | `InstantSearchPage[]`           | Convenience: page results                                    |
| `loading`     | `boolean`                       | `true` while in flight                                       |
| `error`       | `Error \| null`                 | Error, if any                                                |
| `refetch`     | `() => void`                    | Re-run with the same params                                  |

```tsx theme={null}
const { products, suggestions, data } = useInstantSearch(
  { query: term, objects: ['product', 'query_suggestion', 'autocorrect'] },
  { skip: term.length < 2 },
);
```

### useSearchConfig()

Fetches the headless search config (feature flags, filter definitions, shop, analytics,
translations, storefront token).

```typescript theme={null}
function useSearchConfig(
  params?: SearchConfigParams,
  options?: { skip?: boolean }
): UseSearchConfigResult
```

**Returns — `UseSearchConfigResult`**: `{ config: SearchConfigResponse | null, loading, error, refetch }`.

```tsx theme={null}
const { config } = useSearchConfig({ locale: 'en-US' });
```

### useSearchInit() / useSearchInitReady()

`GloodProvider` runs the search session bootstrap **once on mount** (when `search()` is
registered), mirroring recs init. You don't call it yourself — these hooks read its result:

```typescript theme={null}
function useSearchInit(): SearchInitResponse | null
function useSearchInitReady(): boolean
```

* `useSearchInit()` returns the [`SearchInitResponse`](/for-developers/glood-hydrogen-sdk/api-reference/types#searchinitparams--searchinitresponse) (visitor identity, history, lean `config`, `token`, `visit_id`), or `null` until it resolves.
* `useSearchInitReady()` returns `true` once init has settled (success or failure). `useSearch` and `useInstantSearch` gate their fetches on this, so they don't fire before the session is bootstrapped.

```tsx theme={null}
const init = useSearchInit();
const ready = useSearchInitReady();
// init?.visitor.browsed_products, init?.token, init?.visit_id …
```

## Standalone functions

Isomorphic functions for the v1 search suite. Use them in Hydrogen route loaders (SSR) or
anywhere you need the API without a React component. Every function takes
`options: FetchSearchOptions` as its first argument.

### Endpoints

Each function POSTs to a path under the search endpoint (default `https://search.glood.ai`), all
**v1**:

| Function                                    | Method + Path                                          |
| ------------------------------------------- | ------------------------------------------------------ |
| `fetchSearchInit(options, params?)`         | `POST /api/storefront/v1/headless/init`                |
| `fetchSearchConfig(options, params?)`       | `POST /api/storefront/v1/headless/config`              |
| `fetchInstantSearch(options, params)`       | `POST /api/storefront/v1/headless/instant-search`      |
| `fetchFilters(options, params?)`            | `POST /api/storefront/v1/headless/filters`             |
| `fetchFilterAggregations(options, params?)` | `POST /api/storefront/v1/headless/filter-aggregations` |
| `fetchSearchResults(options, params?)`      | `POST /api/storefront/v1/headless/search`              |
| `fetchImageSearch(options, params)`         | `POST /api/storefront/v1/headless/image-search`        |
| `sendSearchEvent(options, params)`          | `POST /api/storefront/v1/headless/events`              |

<Note>
  `FetchSearchOptions` is the shared `HeadlessRequestOptions` — `apiKey` and `myShopifyDomain`
  are required; `endpoint` (default `https://search.glood.ai`) and `debug` are optional. The
  version is pinned to `1` internally. In the browser, `visitor_id` / `client_id` / `session_id`
  default from the Glood cookies when omitted.
</Note>

<Note>
  The standalone functions above POST to whatever `endpoint` you pass in `options`. When you call
  through the **`SearchApp`** (`app.imageSearch(...)`, the `track*` helpers, auto-subscribed events),
  routing is split automatically: `imageSearch` and all events go to the app's **`controlEndpoint`**
  (non-edge), while every other call uses `endpoint`. See
  [Configuration → Edge vs. non-edge routing](/for-developers/glood-hydrogen-sdk/configuration#edge-vs-non-edge-routing-controlendpoint).
</Note>

### fetchSearchInit()

Session bootstrap — resolves the visitor, returns their history (browsed / cart /
purchased product ids), a lean feature-flag config (no filter definitions, no filter tree),
the storefront token and a fresh `visit_id`. `GloodProvider` runs this automatically on
mount; call it directly only for SSR or non-React use.

```typescript theme={null}
async function fetchSearchInit(
  options: FetchSearchOptions,
  params?: SearchInitParams
): Promise<SearchInitResponse>
```

### fetchSearchConfig()

```typescript theme={null}
async function fetchSearchConfig(
  options: FetchSearchOptions,
  params?: SearchConfigParams
): Promise<SearchConfigResponse>
```

### fetchInstantSearch()

`query` is required (may be `""`); throws locally if `query` is `undefined`/`null`.

```typescript theme={null}
async function fetchInstantSearch(
  options: FetchSearchOptions,
  params: InstantSearchParams
): Promise<InstantSearchResponse>
```

### fetchFilters()

```typescript theme={null}
async function fetchFilters(
  options: FetchSearchOptions,
  params?: FiltersParams
): Promise<FiltersResponse>
```

### fetchFilterAggregations()

```typescript theme={null}
async function fetchFilterAggregations(
  options: FetchSearchOptions,
  params?: FilterAggregationsParams
): Promise<FilterAggregationsResponse>
```

### fetchSearchResults()

```typescript theme={null}
async function fetchSearchResults(
  options: FetchSearchOptions,
  params?: SearchParams
): Promise<SearchResponse>
```

```tsx theme={null}
export async function loader({ request, context }) {
  const q = new URL(request.url).searchParams.get('q') ?? '';
  const results = await fetchSearchResults(
    { apiKey: context.env.GLOOD_API_KEY, myShopifyDomain: 'your-store.myshopify.com' },
    { query: q, pagination: { page: 1, limit: 24 } },
  ).catch(() => null);
  return { results };
}
```

### fetchImageSearch()

Exactly one of `image` / `imageUrl` is required (throws locally otherwise).

```typescript theme={null}
async function fetchImageSearch(
  options: FetchSearchOptions,
  params: ImageSearchParams
): Promise<ImageSearchResponse>
```

### sendSearchEvent()

The v1 analogue of `sendHeadlessEvent`. In the browser, `channel` (`'hydrogen'`),
`session_id`, `client_id` and `visitor_id` are filled from cookies when omitted. Extra
snake\_case fields (`cart`, `customer`, `customer_privacy`, …) pass through. For SSR /
non-React use; inside React prefer the `SearchApp` track helpers wired up by `GloodProvider`.

```typescript theme={null}
async function sendSearchEvent(
  options: FetchSearchOptions,
  params: HeadlessEventParams
): Promise<HeadlessEventResponse>
```

```tsx theme={null}
await sendSearchEvent(
  { apiKey, myShopifyDomain },
  {
    event: {
      id: crypto.randomUUID(),
      name: 'glood:search_result_clicked',
      type: 'custom',
      timestamp: new Date().toISOString(),
      // Search events are fully snake_case; context is required (navigator.user_agent).
      context: { navigator: { user_agent: navigator.userAgent } },
      custom_data: { track: { request_id, search_term: 'shoe', products: [{ product_id: '123' }] } },
    },
    channel: 'HYDROGEN',
  },
);
```

<Warning>
  `event.timestamp` must be an ISO 8601 string within ±2 days of now (server rule). The events
  API requires at least one of `visitor_id` / `client_id` (defaulted from cookies in the browser).
</Warning>

### Errors — GloodApiError

Thrown by every standalone function on a non-2xx response (re-exported from the search module):

```typescript theme={null}
import { GloodApiError } from '@glood/hydrogen';

try {
  await fetchSearchResults(options, params);
} catch (error) {
  if (error instanceof GloodApiError && error.status === 401) {
    // Invalid or mismatched API key
  }
}
```

## SearchApp

The app instance registered via `client.use(search())`. Retrieve it from the client for
advanced / SSR usage:

```typescript theme={null}
import { SearchApp } from '@glood/hydrogen';

const app = client.getApp('search') as SearchApp;
```

### Methods

| Method          | Signature                                                         | Description                                                                                        |
| --------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `init`          | `(params?: SearchInitParams) => Promise<SearchInitResponse>`      | Session bootstrap (visitor + history + lean config + token). `GloodProvider` runs it once on mount |
| `config`        | `(params?: SearchConfigParams) => Promise<SearchConfigResponse>`  | Fetch the search config                                                                            |
| `instantSearch` | `(params: InstantSearchParams) => Promise<InstantSearchResponse>` | Instant search                                                                                     |
| `filters`       | `(params?: FiltersParams) => Promise<FiltersResponse>`            | Facet definitions                                                                                  |
| `search`        | `(params?: SearchParams) => Promise<SearchResponse>`              | Full product search                                                                                |
| `imageSearch`   | `(params: ImageSearchParams) => Promise<ImageSearchResponse>`     | Visual search                                                                                      |

```typescript theme={null}
const res = await app.search({ query: 'shoe', pagination: { limit: 24 } });
```

### Event helpers

Each emits a `glood:search:*` custom event via the pixel pipeline and returns `void`:

```typescript theme={null}
trackSearch(track: SearchTrack): void        // glood:instant_search_triggered
trackResultView(track: SearchTrack): void     // glood:search_result_rendered
trackResultClick(track: SearchTrack): void    // glood:search_result_clicked
trackAddToCart(track: SearchTrack): void       // glood:search:add_to_cart
trackFilter(track: SearchTrack): void          // glood:search_filter_updated
```

```typescript theme={null}
app.trackResultClick({
  request_id: res.request_id,
  search_term: 'shoe',
  search_type: 'text',
  products: [{ product_id: '9311227838783', variant_id: '48792371233087', quantity: 1 }],
  filters: [{ key: 'color', value: ['red'] }],
  source: 'search_page',
});
```

<Note>
  Track events respect the app's pixel config: if `pixel.enabled` is `false`, they are not sent.
</Note>

## search() module

Factory that creates and configures the search app. Pass `{ events }` to narrow the standard
Shopify events forwarded to the search backend (default: all events).

```typescript theme={null}
function search(options?: { events?: EventType[] }): GloodAppModule
```

```typescript theme={null}
import { createGlood, search } from '@glood/hydrogen';

const glood = createGlood({ apiKey, myShopifyDomain })
  .use(search({ events: ['search_submitted', 'product_added_to_cart'] }));
```

## SearchTrack object

Attribution payload for `glood:search:*` events (snake\_case wire shape):

```typescript theme={null}
interface SearchTrack {
  request_id?: string;      // request_id from the search response
  search_term?: string;
  search_type?: string;     // 'text' | 'image'
  products?: Array<{ product_id: string | number; variant_id?: string | number; quantity?: number }>;
  filters?: Array<{ key: string; value: string | string[] }>;
  source?: string;
  page_type?: string;
}
```

## Response shapes

The key response types (`SearchResponse`, `InstantSearchResponse`, `SearchConfigResponse`,
`FiltersResponse`, `FilterAggregationsResponse`, `ImageSearchResponse`) and the shared
`SearchProduct` object are documented in full in
[TypeScript Types → Search API Types](/for-developers/glood-hydrogen-sdk/api-reference/types#search-api-types).

### Example — SearchResponse

```json theme={null}
{
  "status": "ok",
  "request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
  "products": [
    {
      "product_id": 9311227838783,
      "title": "Running Shoe",
      "handle": "running-shoe",
      "price": 49.99,
      "compare_at_price": 69.99,
      "display_price": 49.99,
      "display_currency": "USD",
      "available_for_sale": true,
      "is_in_stock": true,
      "image": { "url": "https://cdn.shopify.com/...", "alt_text": "Running Shoe" },
      "options": [{ "id": 111, "name": "Size", "position": 1, "values": ["S", "M", "L"] }],
      "variants": [
        {
          "variant_id": 456,
          "title": "M",
          "price": 49.99,
          "selected_options": [{ "name": "Size", "value": "M" }]
        }
      ],
      "metafields": [{ "namespace": "custom", "key": "material", "value": "leather" }]
    }
  ],
  "pagination": {
    "total": 240, "page": 1, "limit": 24, "total_pages": 10,
    "has_next_page": true, "has_prev_page": false, "next_page": 2, "prev_page": null
  },
  "facets": [
    { "key": "color", "type": "terms", "aggregation": { "buckets": [{ "value": "red", "count": 42, "selected": true }] } }
  ],
  "metadata": { "query_time": 40, "total_time": 55, "region": "toronto", "search_type": "hybrid" }
}
```

## See Also

* [Search & Discovery Guide](/for-developers/glood-hydrogen-sdk/search) — walkthrough with rendering patterns
* [Search Example](/for-developers/glood-hydrogen-sdk/examples/search) — complete working route
* [TypeScript Types](/for-developers/glood-hydrogen-sdk/api-reference/types#search-api-types) — all search type definitions
* [Event System](/for-developers/glood-hydrogen-sdk/api-reference/event-system) — how search events are transmitted
