---
title: Find API
description: Learn how to use the WP Engine Smart Search Find API to search and filter your data.
url: https://developers.wpengine.com/docs/smart-search/graphql-api/find/
image: https://developers.wpengine.com/og/docs/smart-search/graphql-api/find.png
---

# Find API

> **Tip**
>
> New to the Smart Search API? Check out the [Getting Started](https://developers.wpengine.com/docs/smart-search/getting-started) guide to learn how to authenticate and make your first request.

The Find API allows you to search for documents in your index using a powerful GraphQL interface. You can perform full-text searches, apply strict filters, sort results, and use advanced features like semantic search, geographic filtering, and aggregations.

This guide will walk you through the API’s capabilities, starting with basic examples and gradually introducing more advanced topics.

## Basic Search

The simplest way to search is by providing a search term to the `query` parameter. This performs a full-text search across the indexed fields of your documents.

```graphql
query FindSimpleQuery {
  find(query: "comedy sports") {
    total
    documents {
      id
      score
      data
    }
  }
}
```

The query returns a `SearchResult` object containing:

- `total`: The total number of documents that match the query, regardless of pagination.
- `documents`: An array of matching documents, each with its `id`, relevance `score`, and the document `data` (which includes fields like `post_title`, `post_content`, `post_type`, `categories`, `author`, etc.).

## Query vs. Filter: Understanding the Difference

It is crucial to understand the distinction between the `query` and `filter` parameters. Though they can be used together, they serve different purposes.

> **Tip**
>
> **In short: Use `query` for full-text search (like a search bar) and `filter` for strict, structured filtering (like category or type selection).**

### The `query` Parameter

The `query` parameter is primarily designed for **full-text searching**. It uses a flexible and powerful syntax that makes it ideal for processing user-generated search input from a search bar.

When used in conjunction with the `semanticSearch` parameter, the `query` string is used to perform a semantic or hybrid search, searching by meaning and intent rather than just keywords.

The syntax supports the following operators:

- **Logical Operators**:

  - `AND` requires all terms to be present (case-sensitive)
  - `OR` requires at least one term to be present (case-sensitive)
  - `NOT` excludes documents containing the term (case-sensitive)
  - `+` requires a term to be present
  - `|` signifies OR
  - `-` excludes a term

- **Default Operator**: If no operator is specified, `OR` is used by default. For example, `red leather couch` is interpreted as `red | leather | couch`.

- **Phrase Queries**: Wrap text in double quotes (`"`) to search for an exact phrase (e.g., `"gourmet coffee beans"`).

- **Wildcards**: Use `?` to replace a single character and `*` to replace zero or more characters (e.g., `qu?ck bro*`).

- **Precedence Grouping**: Use parentheses (`(` and `)`) to group clauses and control operator precedence. For example, `(red | blue) AND shoes` will search for documents that contain the term `shoes` and either `red` or `blue`.

- **Escaping Characters**: To use one of the special operator characters literally, escape it with a preceding backslash (`\`). Reserved characters include: `+ - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /`

### The `filter` Parameter

The `filter` parameter is used for **strict, structured filtering**. It applies exact-match conditions to narrow down the dataset *before* the main query is executed. It uses a strict parser, and any syntax errors will prevent the query from running.

The syntax supports features like:

- **Field Targeting**: `post_type:post`. You can use dot notation (e.g., `author.user_nicename:"admin"`) to target nested fields.

- **Boolean Operators**: `AND`, `OR`, and `NOT` (also written as `&&`, `||`, and `!`) for combining clauses. For example:

  - `post_type:post AND author.user_nicename:"admin"`
  - `categories.name:Comedy OR categories.name:Drama`
  - `post_type:post AND NOT categories.name:Drama`

- **Required/Prohibited Terms**: Use `+` (must be present) and `-` (must not be present). For example:
  - `+post_type:post -categories.name:Drama` (must be post, must not be Drama category)

- **Grouping**: Use `( )` to group clauses and control precedence. For example:
  - `(categories.name:Comedy OR categories.name:Drama) AND post_type:post`

- **Wildcards**: Use `?` to replace a single character and `*` to replace zero or more characters:

  - `qu?ck` matches “quick” or “quack”
  - `auth*` matches “author”, “authority”, “authentication”, etc.
  - **Note**: A maximum of 5 wildcards (`*` or `?`) are allowed per filter query

- **Ranges**: Inclusive ranges with `[min TO max]` and exclusive ranges with `{min TO max}`:

  - `ID:[10 TO *]` finds documents with ID 10 or greater
  - `post_date_gmt:[2024-01-01 TO 2024-12-31]` finds documents in 2024
  - Simplified syntax: `ID:>10`, `ID:>=10`, `ID:<10`, `ID:<=10`
  - Combined: `ID:(>=10 AND <20)` or `ID:(+>=10 +<20)`

- **Exists Check**: Check for the presence of a field using `_exists_:title`.

- **Escaping Special Characters**: Use backslash (`\`) to escape reserved characters: `+ - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /`
  - Example: `\(1\+1\)\=2` to search for the literal string “(1+1)=2”

Think of `filter` as the tool for applying non-negotiable criteria, such as filtering by a specific category, post type, or other structured data.

### Combining `query` and `filter`

Here is an example that demonstrates how to use both parameters together. This query searches for “modern furniture” but only within documents that are of the `post` type.

```graphql
query CombinedQueryAndFilter {
  find(query: "coach", filter: "post_type:post AND categories.name:Sports") {
    total
    documents {
      id
      data
    }
  }
}
```

## Structuring the Search Query

The `query` parameter supports a rich syntax to give you fine-grained control over your search.

### Logical Operators

You can combine terms with logical operators using symbols or keywords.

- **AND**: Requires all terms to be present. Use the `AND` keyword.

  ```graphql
  query: "comedy AND sports"
  ```

- **OR**: Requires at least one of the terms to be present. Use the `OR` keyword or `|` symbol. If no operator is specified, `OR` is the default.

  ```graphql
  query: "comedy OR drama"
  ```

  Or using the `|` symbol:

  ```graphql
  query: "comedy | drama"
  ```

  Note: If no operator is specified, `OR` is the default:

  ```graphql
  query: "comedy drama" // This is equivalent to "comedy | drama"
  ```

- **NOT**: Excludes documents containing the term. Use the `NOT` keyword or `-` symbol.

  ```graphql
  query: "sports NOT football"
  ```

  Or using the `-` symbol:

  ```graphql
  query: "sports -football"
  ```

- **Required Term**: Use `+` to require a specific term to be present.

  ```graphql
  query: "+coach"
  ```

  This ensures “coach” must be in the results.

You can combine these operators for complex queries:

```graphql
query: "(comedy OR drama) AND (coach NOT soccer)"
```

### Wildcards

Use wildcards for pattern matching.

- **Wildcards**: Use `?` for single character and `*` for multiple characters.

  ```graphql
  query: "qu?ck bro*"
  ```

  This matches “quick brown”, “quack brown”, “quick bronze”, etc.

### Phrase Queries

- **Exact Phrases**: Use double quotes for exact phrase matching.

  ```graphql
  query: "\"quick brown fox\""
  ```

### Grouping and Precedence

Use parentheses to control operator precedence and create complex queries.

```graphql
query: "(quick | fast) AND fox"
```

This requires “fox” and either “quick” or “fast” to be present.

```graphql
query: "(New York | Los Angeles) AND (restaurant | cafe)"
```

This finds documents containing either “New York” or “Los Angeles” AND either “restaurant” or “cafe”.

## Advanced Filtering

### Filtering by Exact Keywords

When filtering on text fields, you may need to distinguish between a partial match and an exact match. For example, a filter on `categories.name:Car` might incorrectly match both “Car” and “Car Sales”.

To ensure an exact match, append `.keyword` to the field name. This forces the filter to match the entire term precisely.

```graphql
query KeywordFilterQuery {
  find(query: "coach", filter: "categories.name.keyword:Comedy") {
    total
    documents {
      id
    }
  }
}
```

This query will only return documents where the category is exactly “Comedy”.

### Filtering by Field

You can target a filter to a specific field.

```graphql
query FieldSpecificFilter {
  find(query: "*", filter: "post_title:\"Ted Lasso\"") {
    total
    documents {
      id
    }
  }
}
```

### Boolean Logic in Filters

Combine multiple filter conditions using boolean operators.

```graphql
query BooleanFilterQuery {
  find(
    query: "*"
    filter: "post_type:post AND (categories.name:Comedy OR categories.name:Sports) AND NOT categories.name:Drama"
  ) {
    total
    documents {
      id
    }
  }
}
```

You can also use the `+` and `-` operators:

```graphql
query RequiredProhibitedFilter {
  find(
    query: "*"
    filter: "+post_type:post -categories.name:Drama +author.user_nicename:admin"
  ) {
    total
    documents {
      id
    }
  }
}
```

### Wildcard Filtering

Use wildcards in filter expressions for pattern matching.

```graphql
query WildcardFilterQuery {
  find(query: "*", filter: "post_title:Ted*") {
    total
    documents {
      id
    }
  }
}
```

This matches titles starting with “Ted” like “Ted Lasso”, “Ted Talk”, etc.

### Range Filtering

You can filter for values within a range in a specific field. Inclusive ranges are specified with square brackets `[min TO max]` and exclusive ranges with curly brackets `{min TO max}`.

#### Numeric Ranges

```graphql
query RangeFilterQuery {
  find(query: "*", filter: "comment_count:[5 TO 19]") {
    total
    documents {
      id
    }
  }
}
```

This finds documents where the `comment_count` field has a value from 5 to 19, inclusive.

You can mix inclusive and exclusive bounds:

```graphql
query MixedRangeFilterQuery {
  find(query: "*", filter: "price:[100 TO 200]") {
    total
    documents {
      id
    }
  }
}
```

This finds prices from 100 (inclusive) up to but not including 200.

#### Unbounded Ranges

```graphql
query UnboundedRangeFilterQuery {
  find(query: "*", filter: "comment_count:[10 TO *]") {
    total
    documents {
      id
    }
  }
}
```

This finds documents where `comment_count` is 10 or greater.

#### Simplified Range Syntax

For simpler range queries, you can use comparison operators:

```graphql
query SimplifiedRangeQuery {
  find(query: "*", filter: "age:>18") {
    total
    documents {
      id
    }
  }
}
```

Available operators: `>`, `>=`, `<`, `<=`

To combine upper and lower bounds:

```graphql
query CombinedRangeQuery {
  find(query: "*", filter: "age:(>=18 AND <65)") {
    total
    documents {
      id
    }
  }
}
```

#### Date Ranges

Filter by date ranges using the same syntax:

```graphql
query DateRangeFilterQuery {
  find(query: "*", filter: "post_date_gmt:[2024-01-01 TO 2024-12-31]") {
    total
    documents {
      id
    }
  }
}
```

This finds documents where the `post_date_gmt` is within the year 2024.

You can also include a time component in the date range:

```graphql
query DateTimeRangeFilterQuery {
  find(
    query: "*"
    filter: "post_date_gmt:[2024-01-01T00:00:00 TO 2024-01-01T12:00:00]"
  ) {
    total
    documents {
      id
    }
  }
}
```

This finds documents published in the first 12 hours of January 1st, 2024.

Or use simplified syntax for dates:

```graphql
query SimpleDateRangeQuery {
  find(query: "*", filter: "post_date_gmt:>2024-01-01") {
    total
    documents {
      id
    }
  }
}
```

## Sorting & Pagination

### Sorting Results (`orderBy`)

The `orderBy` parameter is an array of objects that specify the sorting criteria.

#### By Dates

```graphql
query OrderByDate {
  find(
    query: "my search query"
    orderBy: [{ field: "post_date_gmt", direction: desc }]
  ) {
    documents {
      id
    }
  }
}
```

#### By Strings

To sort by a string field, you must append `.keyword` to the field name.

```graphql
query OrderByString {
  find(
    query: "my search query"
    orderBy: [{ field: "post_title.keyword", direction: asc }]
  ) {
    documents {
      id
    }
  }
}
```

#### By Numbers

```graphql
query OrderByNumber {
  find(
    query: "my search query"
    orderBy: [{ field: "price", direction: asc }]
  ) {
    documents {
      id
    }
  }
}
```

#### By Multiple Clauses

You can provide multiple sorting clauses. Each subsequent clause is used as a tie-breaker.

```graphql
query OrderByMultiple {
  find(
    query: "my search query"
    orderBy: [
      { field: "price", direction: asc }
      { field: "post_date_gmt", direction: desc }
    ]
  ) {
    documents {
      id
    }
  }
}
```

### Pagination

#### Offset/Limit Pagination

Use `offset` and `limit` for simple pagination.

```graphql
query OffsetPagination {
  find(query: "articles", offset: 20, limit: 10) {
    documents {
      id
    }
  }
}
```

#### Cursor Pagination (`searchAfter`)

For more stable, high-performance pagination, use `searchAfter`. This requires a stable sort order. The `_score` field alone is not stable, so it must be combined with a tie-breaker field like a timestamp or unique ID.

First, perform an initial search and retrieve the `sort` values from the last document.

```graphql
query CursorInitialQuery {
  find(
    query: "test"
    orderBy: [{ field: "_score" }, { field: "post_date_gmt", direction: desc }]
  ) {
    documents {
      id
      sort
      data
    }
  }
}
```

The result might include a document like this:

```json
{
  "id": "post:17",
  "sort": ["4.680951", "2024-01-23T11:06:25"]
}
```

Use these `sort` values in the `searchAfter` parameter of your next query to fetch the subsequent page.

```graphql
query CursorNextPageQuery {
  find(
    query: "test"
    orderBy: [{ field: "_score" }, { field: "post_date_gmt", direction: desc }]
    searchAfter: ["4.680951", "2024-01-23T11:06:25"]
  ) {
    documents {
      id
      sort
    }
  }
}
```

## Controlling Search Behavior

### Field Weighting

You can influence the relevance score by assigning weights to different fields.

#### Global Field Weights

The top-level `fields` parameter applies weights to all document types.

```graphql
query GlobalFieldWeights {
  find(
    query: "coach"
    fields: [{ name: "post_title", weight: 2 }, { name: "post_content" }]
  ) {
    documents {
      id
    }
  }
}
```

#### Type-Specific Field Weights

Use `options.fields` for more granular control, assigning different weights based on the document type.

```graphql
query TypeSpecificFieldWeights {
  find(
    query: "search term"
    options: {
      fields: {
        typeFieldName: "post_type"
        types: [
          {
            type: "post"
            fields: [
              { name: "post_title", weight: 3 }
              { name: "post_content", weight: 1 }
            ]
          }
          { type: "page", fields: [{ name: "page_title", weight: 2 }] }
        ]
      }
    }
  ) {
    documents {
      id
    }
  }
}
```

> **Caution**
>
> Type-specific field selection (`options.fields`) completely overrides the global `fields` parameter.

### Search Tolerance (`tolerance`)

By default, the Find API uses **stemming** to match words. Stemming reduces words to their root form, so a search for “running” would also match “run” and “ran”.

If you need to account for misspellings, you can use **fuzzy matching** instead by setting the `tolerance` parameter. This is useful for handling typos in user input.

```graphql
query FuzzySearch {
  find(query: "Austn", tolerance: { name: fuzzy, fuzzyDistance: 1 }) {
    documents {
      id
    }
  }
}
```

In this example, `fuzzyDistance: 1` allows for a one-character difference, so the query for “Austn” would correctly match “Austin”.

### Time Decay Scoring

Boost the relevance of more recent documents using `timeDecay`.

```graphql
query TimeDecaySearch {
  find(
    query: "search term"
    options: {
      timeDecay: [{ field: "post_date", scale: "30d", decayRate: 0.5 }]
    }
  ) {
    documents {
      id
    }
  }
}
```

### Query Rescorer

Improve precision by re-scoring the top documents with a more expensive algorithm, like phrase matching.

```graphql
query QueryRescorer {
  find(
    query: "juicy cucumber"
    options: {
      queryRescorer: {
        windowSize: 10
        fields: ["post_title"]
        queryWeight: 0.9
        rescoreQueryWeight: 1.1
      }
    }
  ) {
    documents {
      id
    }
  }
}
```

### Promoted Documents

Pin specific documents to the top of search results, overriding organic ranking. This feature enables editorial control and merchandising strategies by ensuring priority content appears first, regardless of relevance scoring.

#### Common Use Cases

- **Merchandising & Featured Content**: Promote seasonal products, featured articles, or special offers
- **Campaign-Based Results**: Surface specific content for marketing campaigns or events
- **Emergency Updates**: Ensure critical information (e.g., service alerts, breaking news) is immediately visible
- **A/B Testing**: Test different content placements for different user segments
- **Personalized Results**: Customize search results based on user preferences or behavior

#### Basic Example

```graphql
query PromotedDocuments {
  find(
    query: "laptop"
    options: {
      promotions: { documents: ["post:123", "product:456", "page:789"] }
    }
  ) {
    total
    documents {
      id
      score
      data
    }
  }
}
```

In this example, the three specified documents will always appear at the top of results in the order provided (`post:123` first, then `product:456`, then `page:789`), followed by organic search results matching “laptop”.

> **Tip**
>
> **Promoted documents always appear first in the exact order you specify**, regardless of their relevance score or any sorting criteria. This gives you complete control over result positioning for strategic business needs.

#### How Promotions Work with Other Parameters

```graphql
query PromotionsWithFilter {
  find(
    query: "tech"
    filter: "post_type:post AND categories.name:Technology"
    options: {
      promotions: { documents: ["post:123", "post:456", "product:789"] }
    }
  ) {
    documents {
      id
    }
  }
}
```

In this example, all three promoted documents (`post:123`, `post:456`, and `product:789`) will appear at the top of the results **regardless of the filter criteria**. Promoted documents bypass all filters and are always displayed first, even if `product:789` is not of type `post` or doesn’t have the `Technology` category.

**With Sorting (orderBy)**

Promoted documents always appear before organic results, regardless of sorting criteria. However, the sorting you specify still applies to the organic results that follow the promoted documents.

```graphql
query PromotionsWithSorting {
  find(
    query: "articles"
    orderBy: [{ field: "post_date_gmt", direction: desc }]
    options: { promotions: { documents: ["post:100", "post:200"] } }
  ) {
    documents {
      id
      data
    }
  }
}
```

Result order: `post:100`, `post:200`, then organic results sorted by date (newest first).

**With Pagination**

Promoted documents are included in the pagination limits and count toward the `total`. They appear at the top of the first page.

```graphql
query PromotionsWithPagination {
  find(
    query: "products"
    limit: 10
    offset: 0
    options: {
      promotions: { documents: ["product:1", "product:2", "product:3"] }
    }
  ) {
    total
    documents {
      id
    }
  }
}
```

The first page will show the 3 promoted products plus 7 organic results (if available). If you use `offset: 10` for the second page, you’ll get only organic results (the promoted documents only appear on the first page when offset is 0).

**With Cursor Pagination (searchAfter)**

When using cursor-based pagination, promoted documents appear in the initial results. Subsequent pages using `searchAfter` will contain only organic results.

First page (with promoted document):

```graphql
query PromotionsFirstPage {
  find(
    query: "articles"
    orderBy: [{ field: "_score" }, { field: "post_date_gmt", direction: desc }]
    options: { promotions: { documents: ["post:999"] } }
    limit: 10
  ) {
    documents {
      id
      sort
    }
  }
}
```

Result: `post:999` appears first, followed by 9 organic results. The last document might return:

```json
{
  "id": "post:42",
  "sort": ["8.123456", "2024-02-15T10:30:00"]
}
```

Second page (using `searchAfter` - no promoted documents):

```graphql
query PromotionsNextPage {
  find(
    query: "articles"
    orderBy: [{ field: "_score" }, { field: "post_date_gmt", direction: desc }]
    searchAfter: ["8.123456", "2024-02-15T10:30:00"]
    limit: 10
  ) {
    documents {
      id
      sort
    }
  }
}
```

Result: Only organic results (promoted documents don’t appear on subsequent pages).

#### Validation and Error Handling

The API enforces strict validation rules for promoted documents:

##### Document ID Format

Document IDs are string identifiers for your documents. While any string format is technically valid, the API validates against certain characters to ensure compatibility.

- IDs must be non-empty strings
- IDs **cannot contain** these characters: `/` `\` `?` `#` `.` or spaces
- Duplicate IDs in the same request are allowed, but the duplicated entry will be ignored (single document can only be promoted once)
- Maximum 100 documents per query
- **Empty arrays are allowed** - passing an empty array `[]` is treated as a no-op (no documents will be promoted)
- **Null/nil values are not allowed** - the field cannot be null/nil

> **Recommended Format**
>
> While any format works, using `type:id` (e.g., `post:123`) is recommended for clarity and consistency with Elasticsearch document IDs.

##### Required Field Validation

The `documents` field has the following validation rules:

**Scenario 1: Null/Nil Value**

If you attempt to pass `nil` or `null` for the `documents` field, GraphQL validation will fail before reaching the backend.

```graphql
query InvalidNullPromotions {
  find(
    query: "search term"
    options: {
      promotions: { documents: nil } # ERROR: Null not allowed
    }
  ) {
    documents {
      id
    }
  }
}
```

See the [Null/Nil documents field](https://developers.wpengine.com/docs/smart-search/graphql-api/find/#common-error-responses) error in Common Error Responses below.

**Scenario 2: Empty Array**

Empty arrays are allowed and treated as a no-op (no documents will be promoted). This is useful when dynamically building queries where you may not always have documents to promote.

```graphql
query EmptyPromotions {
  find(
    query: "search term"
    options: {
      promotions: { documents: [] } # Valid: No-op, no documents promoted
    }
  ) {
    documents {
      id
    }
  }
}
```

This query will execute successfully and return only organic search results.

**Valid examples:**

- `post:123` - Standard format with numeric ID
- `product:abc-456` - Alphanumeric with hyphens
- `page:789` - Simple numeric
- `custom-type:my-item` - Custom type with hyphens
- `article_2024_01` - Underscore format

**Invalid examples:**

- `post/123` - Contains `/`
- `page:my.item` - Contains `.`
- `product:item 456` - Contains space
- `article?123` - Contains `?`
- `path\\file` - Contains `\`
- Empty string - Cannot be empty
- `["post:123", "post:123"]` - Duplicate IDs

**Maximum Limit**

You can promote up to **100 documents** per query. Exceeding this limit will result in an error.

#### Common Error Responses

**Null/Nil documents field:**

```json
{
  "errors": [
    {
      "message": "Expected value of type \"[ID!]!\", found nil.",
      "locations": [
        {
          "line": 7,
          "column": 19
        }
      ],
      "extensions": {
        "code": "GRAPHQL_VALIDATION_FAILED"
      }
    }
  ]
}
```

**Invalid document ID format:**

```json
{
  "errors": [
    {
      "message": "invalid document ID format: '123' (must be 'type:id')",
      "path": ["find"]
    }
  ]
}
```

**Duplicate document ID:**

```json
{
  "errors": [
    {
      "message": "duplicate document ID: \"post:1\"",
      "path": ["find"]
    }
  ]
}
```

**Too many documents:**

```json
{
  "errors": [
    {
      "message": "too many promoted documents: 150 (maximum 100 allowed)",
      "path": ["find"]
    }
  ]
}
```

**Empty ID string:**

```json
{
  "errors": [
    {
      "message": "invalid document ID: \"\" (ID cannot be empty, contain: / \\ ? # . or spaces)",
      "path": ["find"]
    }
  ]
}
```

#### Advanced Usage Examples

**Seasonal Campaign with Semantic Search**

Combine promotions with semantic search to promote specific products while maintaining intelligent organic results:

```graphql
query SeasonalCampaign {
  find(
    query: "holiday gifts for tech lovers"
    semanticSearch: { searchBias: 7, fields: ["post_title", "post_content"] }
    filter: "post_type:product AND in_stock:true"
    options: {
      promotions: {
        documents: [
          "product:holiday-tech-bundle"
          "product:premium-headphones"
          "product:smart-home-starter"
        ]
      }
    }
    limit: 20
  ) {
    documents {
      id
      score
      data
    }
  }
}
```

**A/B Testing Pattern**

Different user segments can receive different promoted content:

```graphql
# Variant A - Control Group
query ControlGroup {
  find(
    query: "running shoes"
    options: {
      promotions: {
        documents: ["product:bestseller-1", "product:bestseller-2"]
      }
    }
  ) {
    documents {
      id
    }
  }
}


# Variant B - Test Group
query TestGroup {
  find(
    query: "running shoes"
    options: {
      promotions: {
        documents: ["product:new-arrival-1", "product:new-arrival-2"]
      }
    }
  ) {
    documents {
      id
    }
  }
}
```

**Dynamic Emergency Updates**

Surface critical information immediately:

```graphql
query EmergencyAlert {
  find(
    query: "service status"
    options: {
      promotions: {
        documents: ["alert:outage-2024-01-15", "page:status-dashboard"]
      }
    }
    filter: "post_type:(alert OR page OR post)"
  ) {
    documents {
      id
      data
    }
  }
}
```

**Merchandising with Aggregations**

Promote featured products while still providing faceted navigation:

```graphql
query MerchandisingWithFacets {
  find(
    query: "laptops"
    filter: "post_type:product"
    options: {
      promotions: {
        documents: ["product:featured-laptop-1", "product:featured-laptop-2"]
      }
    }
    aggregate: {
      terms: [
        { field: "brand.keyword", size: 10 }
        { field: "price_range.keyword", size: 5 }
      ]
    }
  ) {
    total
    documents {
      id
      data
    }
    aggregations {
      terms {
        field
        terms {
          term
          count
        }
      }
    }
  }
}
```

#### Performance and Best Practices

> **Caution**
>
> **Performance Considerations**
>
> While you can promote up to 100 documents, be mindful of the impact on user experience and performance:
>
> - **Recommended Range**: Typically promote **3-10 documents** for optimal user experience
> - **User Experience**: Too many promoted documents reduce organic result visibility and may frustrate users looking for naturally relevant content
> - **Query Performance**: Promoted documents are efficiently handled, but excessive promotions may increase query complexity

**Best Practices**

1. **Strategic Selection**: Only promote content that provides clear value to users in that specific search context
2. **Regular Rotation**: Update promoted documents regularly to keep results fresh and relevant
3. **Monitor Performance**: Track click-through rates and engagement metrics for promoted documents to measure effectiveness
4. **Test Thoroughly**: Use A/B testing to validate that promotions improve user engagement and conversion
5. **Document Existence**: Ensure promoted document IDs exist in your index; non-existent IDs are silently ignored
6. **Order Matters**: The order of documents in the array determines their display order—most important content first

**Monitoring and Analytics**

To measure the effectiveness of your document promotions:

- Track which promoted documents receive clicks vs. organic results
- Monitor conversion rates for promoted vs. organic content
- Measure time-on-page for promoted documents
- A/B test different promotion strategies to optimize for your KPIs
- Use the `meta` parameter to tag queries with campaign identifiers for easier analysis

```graphql
query TrackedPromotion {
  find(
    query: "winter sale"
    options: { promotions: { documents: ["product:winter-featured-1"] } }
    meta: {
      action: "seasonal-campaign"
      system: "web-frontend"
      source: "winter-2024"
    }
  ) {
    documents {
      id
    }
  }
}
```

### Custom Search Results

Pin specific documents to the top of search results for particular search queries. Unlike promotions (which appear for all queries), custom results only appear when users search for specific terms you configure.

#### Common Use Cases

- **Query-Specific Recommendations**: Show relevant guides when users search for specific topics
- **Contextual Merchandising**: Promote different products based on what users are searching for
- **Guided Navigation**: Direct users to the right resources based on their search intent
- **Knowledge Base Optimization**: Ensure the best help articles appear for common support queries
- **Campaign Targeting**: Show specific content only when users search for campaign-related terms

#### Basic Example

```graphql
query CustomSearchResults {
  find(
    query: "cucumber"
    options: {
      customResults: [{ query: "cucumber", documents: ["post:10", "post:11"] }]
    }
  ) {
    total
    documents {
      id
      score
      data
    }
  }
}
```

In this example, when a user searches for “cucumber”, posts 10 and 11 will appear at the top of the results. If they search for any other term, these custom results won’t appear.

> **Tip**
>
> **Custom results use case-insensitive exact matching.** A configuration with `query: "cucumber"` will match user searches for “cucumber”, “CUCUMBER”, “Cucumber”, etc., but won’t match “cucumbers” or “pickled cucumber”.

#### How Custom Results Work

**Query Matching**

The `query` field in your configuration is matched against the user’s search term:

- **Case-insensitive**: “Cucumber” matches “cucumber” matches “CUCUMBER”
- **Exact match**: “cucumber” does NOT match “cucumbers” or “cucumber salad”
- **Whitespace trimmed**: Leading/trailing spaces are removed before matching

**Result Ordering**

Search results appear in this priority order:

1. **Promotions** (if configured) - always first
2. **Custom Results** (if query matches) - second
3. **Organic search results** - remaining results

> **Caution**
>
> **Important**: Both promotions and custom results bypass all filters and always appear first. Use them strategically to avoid showing irrelevant content to users.

#### Multiple Configurations for the Same Query

You can provide multiple custom result configurations for the same query, and they will be automatically merged:

```graphql
query MergedCustomResults {
  find(
    query: "fox"
    options: {
      customResults: [
        { query: "fox", documents: ["post:10", "page:5", "post:11"] }
        { query: "fox", documents: ["post:20", "page:6"] }
      ]
    }
  ) {
    documents {
      id
    }
  }
}
```

Result order:

1. `post:10`
2. `page:5`
3. `post:11`
4. `post:20`
5. `page:6`
6. Organic search results…

Duplicates are automatically removed while preserving order. The first occurrence of each document is kept.

#### How Custom Results Work with Other Parameters

**With Promotions**

Promotions always appear first, followed by custom results:

```graphql
query CustomResultsWithPromotions {
  find(
    query: "fox"
    options: {
      promotions: { documents: ["product:1", "post:99"] }
      customResults: [{ query: "fox", documents: ["post:10", "page:5"] }]
    }
  ) {
    documents {
      id
    }
  }
}
```

Result order:

1. `product:1` (promotion)
2. `post:99` (promotion)
3. `post:10` (custom result)
4. `page:5` (custom result)
5. Organic search results…

**With Filters**

Custom results bypass all filters, similar to promotions:

```graphql
query CustomResultsWithFilter {
  find(
    query: "cucumber"
    filter: "post_type:post AND categories.name:Vegetables"
    options: {
      customResults: [{ query: "cucumber", documents: ["post:10", "page:5"] }]
    }
  ) {
    documents {
      id
    }
  }
}
```

Both `post:10` and `page:5` will appear at the top, even if `page:5` doesn’t match the filter criteria.

**With Sorting (orderBy)**

Custom results appear before organic results, regardless of sorting:

```graphql
query CustomResultsWithSorting {
  find(
    query: "articles"
    orderBy: [{ field: "post_date_gmt", direction: desc }]
    options: {
      customResults: [
        { query: "articles", documents: ["post:100", "post:200"] }
      ]
    }
  ) {
    documents {
      id
    }
  }
}
```

Result order: `post:100`, `post:200`, then organic results sorted by date (newest first).

**Non-Matching Queries**

When the user’s query doesn’t match any custom result configuration, only organic results are returned:

```graphql
query NoMatchingCustomResults {
  find(
    query: "cucumber"
    options: {
      customResults: [{ query: "fox", documents: ["post:1", "post:11"] }]
    }
  ) {
    documents {
      id
    }
  }
}
```

Result: Only organic search results (the custom results don’t apply because “cucumber” doesn’t match “fox”).

#### Validation and Error Handling

The API enforces strict validation rules for custom results:

**Configuration Limits**

> **Note**
>
> **Validation Rules:**
>
> - Maximum **100 custom result configurations** per request
>
> - Each configuration **cannot be nil/null** — every entry in the array must be a valid configuration object
>
> - Each configuration must have a **non-empty query** (whitespace-only queries are invalid)
>
> - Each configuration can include **0-100 documents** (empty arrays are allowed and treated as a no-op)
>
> - Document IDs must follow the same format rules as promotions:
>
>   - Cannot be empty
>   - Cannot contain: `/` `\` `?` `#` `.` or spaces
>   - Recommended format: `type:id` (e.g., `post:123`, `page:5`)

**Common Error Responses**

**Too Many Configurations:**

```json
{
  "errors": [
    {
      "message": "custom results can include a maximum of 100 configurations, got 101",
      "path": ["find"],
      "extensions": {
        "code": "ATLAS_SEARCH_ERROR"
      }
    }
  ],
  "data": null
}
```

**Nil Configuration:**

```json
{
  "errors": [
    {
      "message": "custom results configuration 0 cannot be nil",
      "path": ["find"],
      "extensions": {
        "code": "ATLAS_SEARCH_ERROR"
      }
    }
  ],
  "data": null
}
```

**Empty Query:**

```json
{
  "errors": [
    {
      "message": "custom results configuration 0 has empty query",
      "path": ["find"],
      "extensions": {
        "code": "ATLAS_SEARCH_ERROR"
      }
    }
  ],
  "data": null
}
```

**Too Many Documents:**

```json
{
  "errors": [
    {
      "message": "custom results configuration 0 can include a maximum of 100 documents, got 101",
      "path": ["find"],
      "extensions": {
        "code": "ATLAS_SEARCH_ERROR"
      }
    }
  ],
  "data": null
}
```

**Invalid Document ID:**

```json
{
  "errors": [
    {
      "message": "custom results configuration 0 has invalid document ID: \"post/10\" (ID cannot be empty, contain: / \\ ? # . or spaces)",
      "path": ["find"],
      "extensions": {
        "code": "ATLAS_SEARCH_ERROR"
      }
    }
  ],
  "data": null
}
```

#### Advanced Usage Examples

**Multi-Query Strategy**

Configure different results for different search terms:

```graphql
query MultiQueryCustomResults {
  find(
    query: "installation"
    options: {
      customResults: [
        {
          query: "installation"
          documents: ["guide:quick-start", "guide:install"]
        }
        { query: "setup", documents: ["guide:setup-wizard", "guide:config"] }
        {
          query: "troubleshooting"
          documents: ["faq:common-issues", "guide:debug"]
        }
      ]
    }
  ) {
    documents {
      id
      data
    }
  }
}
```

Only the configuration matching “installation” will be applied. The others are ignored.

**Seasonal Campaign Targeting**

Show different products for specific search terms:

```graphql
query SeasonalCustomResults {
  find(
    query: "holiday gifts"
    semanticSearch: { searchBias: 7, fields: ["post_title", "post_content"] }
    options: {
      customResults: [
        {
          query: "holiday gifts"
          documents: [
            "product:gift-bundle-1"
            "product:gift-bundle-2"
            "product:holiday-special"
          ]
        }
      ]
    }
    limit: 20
  ) {
    documents {
      id
      data
    }
  }
}
```

**Knowledge Base Optimization**

Ensure users find the right help articles:

```graphql
query KnowledgeBaseCustomResults {
  find(
    query: "password reset"
    filter: "post_type:help_article"
    options: {
      customResults: [
        {
          query: "password reset"
          documents: ["article:reset-password", "article:account-security"]
        }
      ]
    }
  ) {
    documents {
      id
      data
    }
  }
}
```

**A/B Testing Query-Specific Results**

Test different content for the same search term:

```graphql
# Variant A
query VariantA {
  find(
    query: "best practices"
    options: {
      customResults: [
        {
          query: "best practices"
          documents: ["guide:practices-v1", "guide:examples-v1"]
        }
      ]
    }
  ) {
    documents {
      id
    }
  }
}


# Variant B
query VariantB {
  find(
    query: "best practices"
    options: {
      customResults: [
        {
          query: "best practices"
          documents: ["guide:practices-v2", "guide:tutorial"]
        }
      ]
    }
  ) {
    documents {
      id
    }
  }
}
```

#### Performance and Best Practices

> **Caution**
>
> **Performance Considerations**
>
> - **Recommended Range**: Keep configurations focused—typically **3-10 documents per query** for optimal UX
> - **Deduplication**: Automatic but adds minimal processing overhead

**Best Practices**

1. **Specific Terms**: Use specific search terms that users actually search for, not broad generic terms

   - ✅ Good: `query: "how to install wordpress"`
   - ❌ Bad: `query: "the"` or `query: "install"`

2. **Exact Matching**: Remember that matching is exact (case-insensitive). Plan for variations:
   - If users search “install” and “installation”, create separate configurations

3. **Quality Over Quantity**: Pin only the most relevant, high-quality content

4. **Monitor Performance**: Track engagement metrics for custom results vs. organic results

5. **Regular Updates**: Keep configurations current—remove outdated content, add new resources

6. **Test Thoroughly**: Verify your configurations match actual user search patterns

7. **Document Existence**: Ensure pinned documents exist; non-existent IDs are silently ignored

#### Custom Results vs. Promotions

| Feature              | Promotions                                        | Custom Results                         |
| -------------------- | ------------------------------------------------- | -------------------------------------- |
| **When they appear** | Every query                                       | Only when query matches                |
| **Use case**         | Universal pinning (e.g., site-wide announcements) | Query-specific recommendations         |
| **Configuration**    | Single list of documents                          | Multiple query → document mappings     |
| **Priority**         | Highest (always first)                            | Second (after promotions)              |
| **Complexity**       | Simple                                            | More flexible, requires query planning |

**When to use what:**

- **Use Promotions**: For content that should always appear (announcements, featured products, critical updates)
- **Use Custom Results**: For query-specific recommendations (guides for specific topics, products for specific searches)
- **Use Both**: Combine them—promotions for universal content, custom results for targeted recommendations

## Advanced Search Capabilities

### Semantic / Hybrid Search

Leverage machine learning to search by meaning and intent, not just keywords.

> **Tip**
>
> Before using semantic search, you need to configure it for your index. See the [Semantic Search Config API](https://developers.wpengine.com/docs/smart-search/graphql-api/semantic-search-config) guide to learn how to set up semantic search and configure which fields to use.

```graphql
query FindWithSemanticSearch {
  find(
    query: "american coach managing british team"
    semanticSearch: { searchBias: 10, fields: ["post_title", "post_content"] }
  ) {
    documents {
      id
    }
  }
}
```

The `searchBias` parameter controls the balance between semantic and keyword search:

- `0` = Full-text search only
- `10` = Semantic search only
- `1-9` = Hybrid search with varying weights

### Geographic Search

Filter documents based on geographic location using the `geoConstraints` parameter. You can provide one or more circular or rectangular areas to search within.

```graphql
query GeoSearch {
  find(
    query: "coffee shop"
    geoConstraints: {
      circles: [
        { center: { lat: 37.7749, lon: -122.4194 }, maxDistance: "5km" }
      ]
    }
  ) {
    documents {
      id
    }
  }
}
```

This example finds documents tagged “coffee shop” within a 5km radius of the specified coordinates.

### Facets (Aggregations)

Aggregate data to create faceted navigation. You can get term counts or range buckets.

#### Terms Aggregation

Get a count of unique terms in a field.

```graphql
query TermsAggregation {
  find(query: "sport", aggregate: { terms: [{ field: "categories.name" }] }) {
    aggregations {
      terms {
        field
        terms {
          term
          count
        }
      }
    }
  }
}
```

To filter by a facet, use the `filter` parameter.

```graphql
query FilteredTermsAggregation {
  find(
    query: "coach"
    filter: "categories.name:Sports"
    aggregate: { terms: [{ field: "categories.name" }] }
  ) {
    total
    aggregations {
      terms {
        field
        terms {
          term
          count
        }
      }
    }
  }
}
```

#### Ranges Aggregation

Group documents into numeric or date ranges.

```graphql
query RangesAggregation {
  find(
    query: "*"
    aggregate: {
      ranges: [
        {
          field: "price"
          ranges: [{ from: 0, to: 100 }, { from: 101, to: 200 }]
        }
      ]
    }
  ) {
    aggregations {
      ranges {
        field
        ranges {
          from
          to
          count
        }
      }
    }
  }
}
```

## Controlling the Response

### Field Inclusion and Exclusion

Control which fields are returned in the `data` object to reduce response size.

- `includeFields`: Whitelist of fields to return.
- `excludeFields`: Blacklist of fields to omit.

```graphql
query FieldSelection {
  find(
    query: "blog posts"
    options: {
      includeFields: ["post_title", "post_excerpt", "post_date", "author"]
    }
  ) {
    documents {
      id
      data
    }
  }
}
```

### Field Truncation

Truncate long text fields in the response to reduce payload size. This is especially useful for large fields like `post_content` or `post_excerpt` when you only need a preview or summary.

#### Basic Truncation

By default, fields are truncated to 200 characters:

```graphql
query BasicTruncation {
  find(
    query: "wordpress"
    fields: [{ name: "post_content" }]
    options: { truncateFields: [{ field: "post_content" }] }
  ) {
    documents {
      id
      data
    }
  }
}
```

#### Custom Character Limit

Specify a custom character limit for each field:

```graphql
query CustomTruncation {
  find(
    query: "wordpress"
    options: {
      truncateFields: [
        { field: "post_content", maxLength: 100 }
        { field: "post_excerpt", maxLength: 50 }
      ]
    }
  ) {
    documents {
      id
      data
    }
  }
}
```

#### How Truncation Works

- Truncated strings append `"..."` (adds 3 characters to the length)
- Uses UTF-8 character counting (not bytes), so emojis and unicode characters count as single characters
- Only affects string fields in the response `data` object
- Non-string fields and non-existent fields are ignored
- Fields shorter than `maxLength` are unchanged
- If `maxLength` is not specified, defaults to 200 characters

#### When to Use Truncation

- **Large text fields**: Content-heavy fields like `post_content`, `post_excerpt`, or `description`
- **Mobile clients**: Reduce bandwidth usage on mobile devices or slow connections
- **Preview views**: Display summaries or previews where full content isn’t needed
- **Performance optimization**: Testing shows \~25-37% payload reduction for content-heavy queries

#### Combining with Field Selection

You can combine truncation with field inclusion/exclusion for maximum control:

```graphql
query TruncationWithFieldSelection {
  find(
    query: "articles"
    options: {
      includeFields: ["post_title", "post_content", "post_excerpt", "post_date"]
      truncateFields: [
        { field: "post_content", maxLength: 150 }
        { field: "post_excerpt", maxLength: 75 }
      ]
    }
  ) {
    documents {
      id
      data
    }
  }
}
```

## API Reference

The schema defines the GraphQL query type for searching documents in an index. It includes various parameters to customize the search query.

### Query

| Field | Description                                                      |
| ----- | ---------------------------------------------------------------- |
| find  | Searches for documents in an index based on specific parameters. |

#### Find Parameters

| Parameter      | Type                | Description                                                                                                         |
| -------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------- |
| query          | String!             | The search query string, supporting Simple Query String syntax for full-text search. This is required.              |
| filter         | String              | A strict filter string (using Query String syntax) to narrow down the documents to be searched.                     |
| geoConstraints | GeoConstraintsInput | Multiple geographic constraints for proximity-based or area-based filtering with OR logic.                          |
| orderBy        | \[OrderBy]          | An array of fields to sort the results by. Defaults to relevance score (`_score`).                                  |
| searchAfter    | \[String!]          | A set of sort values for cursor-based pagination.                                                                   |
| offset         | Int                 | The number of results to skip for offset-based pagination.                                                          |
| limit          | Int                 | The maximum number of results to return.                                                                            |
| fields         | \[SearchField]      | A global list of fields to search in, with optional weights. Overridden by `options.fields`.                        |
| tolerance      | SearchOption        | The search tolerance, such as `fuzzy` or `stemming`. Defaults to stemming.                                          |
| meta           | MetaInput           | Optional metadata for logging purposes.                                                                             |
| aggregate      | AggregateInput      | Defines aggregations (facets) to be computed on the result set.                                                     |
| semanticSearch | SemanticSearchInput | Configuration for performing semantic or hybrid search.                                                             |
| options        | OptionsInput        | Additional options for controlling search behavior, like type-specific fields, time decay, and document promotions. |

#### SearchField Input

| Field  | Type    | Description                                                         |
| ------ | ------- | ------------------------------------------------------------------- |
| name   | String! | The field name to search for in the document.                       |
| weight | Int     | The weight of the field, affecting the order of returned documents. |

#### MetaInput

Optional meta data input for logging

| Field  | Type   | Description                 |
| ------ | ------ | --------------------------- |
| action | String | Performed action e.g. index |
| system | String | The requester system name   |
| source | String | The requester hostname      |

#### AggregateInput

| Field  | Type                   | Description                  |
| ------ | ---------------------- | ---------------------------- |
| terms  | \[TermAggregateInput]  | Aggregation based on terms.  |
| ranges | \[RangeAggregateInput] | Aggregation based on ranges. |

#### TermAggregateInput

| Field    | Type    | Description                                                                                                                                              |
| -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| field    | String! | Field name we want to aggregate on                                                                                                                       |
| size     | Int     | To retrieve additional terms, employ the “size” parameter. By default, the terms aggregation fetches the top ten terms with the highest document counts. |
| minCount | Int     | If minCount is zero, zero count results are returned (else we omit them)                                                                                 |

#### RangeAggregateInput

| Field  | Type          | Description                                                                                                                                              |
| ------ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| field  | String!       | Field name we want to aggregate on                                                                                                                       |
| ranges | \[RangeInput] | Range Input options                                                                                                                                      |
| size   | Int           | To retrieve additional terms, employ the “size” parameter. By default, the terms aggregation fetches the top ten terms with the highest document counts. |

#### RangeInput

A multi-bucket value source based aggregation that enables the user to define a set of ranges - each representing a bucket

| Field | Type  | Description            |
| ----- | ----- | ---------------------- |
| to    | Float | From value (Inclusive) |
| from  | Float | To value (Exclusive)   |

#### OrderBy Input

| Field        | Type             | Description                                                                                             |
| ------------ | ---------------- | ------------------------------------------------------------------------------------------------------- |
| field        | String!          | The field to order the documents by.                                                                    |
| direction    | OrderByDirection | The sort direction (asc or desc).                                                                       |
| unmappedType | String           | **Deprecated:** Going to be removed soon. When its present default weight score is applied for ordering |

#### SearchOption Input

| Field         | Type              | Description                                                                                                                                                              |
| ------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| name          | SearchOptionEnum! | The search option name (fuzzy or stemming).                                                                                                                              |
| fuzzyDistance | Int               | Optional fuzzy distance. Applicable only if the fuzzy search option is selected. It represents the number of one-character changes needed to turn one term into another. |

#### SemanticSearchInput

Semantic Search query input

| Field      | Type                 | Description                                                                                                                                                    |
| ---------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| searchBias | Int!                 | The search bias of the semantic search query vs full text search. 0 = Full text search only, 10 = Semantic search only. 1-9 mix of both weighted respectfully. |
| fields     | \[String!]!          | Fields for search                                                                                                                                              |
| type       | SEMANTIC_SEARCH_TYPE | Semantic Search type                                                                                                                                           |

#### OptionsInput

Additional options for controlling search behavior and result formatting

<!-- cspell:words rescorer -->

| Field          | Type                   | Description                                                                                                                                                                                                                         |
| -------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| fields         | TypeFields             | Type-specific field searching. Allows you to specify different fields to search for different document types (e.g., search “title” and “content” for post_type:post, but “name” and “description” for post_type:page).              |
| includeFields  | \[String!]             | Fields to include in the search result. When specified, only these fields will be returned in the document data.                                                                                                                    |
| excludeFields  | \[String!]             | Fields to exclude from the search result. These fields will be omitted from the document data.                                                                                                                                      |
| timeDecay      | \[TimeDecayInput!]     | Time-based decay functions to apply to search scoring. Allows boosting more recent documents.                                                                                                                                       |
| queryRescorer  | QueryRescorerInput     | Query rescorer configuration for improving search precision by re-scoring top documents.                                                                                                                                            |
| promotions     | Promotions             | Optional document promotion configuration to pin specific documents at the top of search results, overriding organic ranking.                                                                                                       |
| customResults  | \[CustomResults!]      | Optional query-specific custom results configuration to pin specific documents when users search for particular terms. Multiple configurations can be provided, each specifying which documents to pin for a specific search query. |
| truncateFields | \[TruncateFieldInput!] | Truncate long text fields in the response to reduce payload size. Useful for large fields like post_content or post_excerpt.                                                                                                        |

#### TypeFields Input

Allows you to specify different search fields for different document types. This is useful when different content types have different field structures.

| Field         | Type      | Description                                                                        |
| ------------- | --------- | ---------------------------------------------------------------------------------- |
| typeFieldName | String    | The name of the field that contains the type information. Defaults to “post_type”. |
| types         | \[Type!]! | List of type-specific field configurations.                                        |

#### Type Input

Defines which fields to search for a specific document type.

| Field  | Type             | Description                                                         |
| ------ | ---------------- | ------------------------------------------------------------------- |
| type   | String!          | The type name (e.g., “post”, “page”, “product”).                    |
| fields | \[SearchField!]! | The fields to search for this specific type, with optional weights. |

#### TimeDecayInput

Input for applying continuous, time-based decay to search scores

| Field     | Type    | Description                                                                                                                                                                 |
| --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| field     | String  | The field to apply the decay function to. Defaults to ‘post_date’ if not specified.                                                                                         |
| scale     | String! | The duration from ‘now’ at which the score multiplier will drop. Must match format like ‘30d’, ‘12h’, ‘90m’, ‘3600s’. Defaults to ‘14d’ if not specified. This is required. |
| decayRate | Float   | The score multiplier at the ‘scale’ distance. Defaults to 0.25 if not provided. Value should be between 0 and 1.                                                            |
| offset    | String  | Sets a time offset from ‘now’ before the decay begins. Defaults to ‘7d’. Example: ‘7d’. Uses same format as scale.                                                          |

#### GeoConstraintsInput

Multiple geographic constraints for query results with OR logic combination:

- Multiple circles: OR (matches if within ANY circle)
- Multiple bounding boxes: OR (matches if within ANY bounding box)
- Circles and bounding boxes: OR (matches if within ANY circle OR ANY bounding box)

| Field         | Type                      | Description                                                                                                                     |
| ------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| circles       | \[CircleConstraint!]      | Circle constraints - results must be within specified distance from center points. Multiple circles are combined with OR logic. |
| boundingBoxes | \[BoundingBoxConstraint!] | Bounding box constraints - results must be within rectangular areas. Multiple bounding boxes are combined with OR logic.        |

#### CircleConstraint

Circular area constraint defined by center point and maximum distance.

| Field       | Type           | Description                        |
| ----------- | -------------- | ---------------------------------- |
| center      | GeoPointInput! | Center point of the circular area  |
| maxDistance | Distance!      | Maximum distance from center point |

#### BoundingBoxConstraint

Bounding box rectangular area defined by southwest and northeast corners.

| Field     | Type           | Description                                                                             |
| --------- | -------------- | --------------------------------------------------------------------------------------- |
| southwest | GeoPointInput! | Southwest corner of the bounding box. This is the minimum latitude and longitude point. |
| northeast | GeoPointInput! | Northeast corner of the bounding box. This is the maximum latitude and longitude point. |

#### GeoPointInput

Geographic coordinate input

| Field | Type   | Description                                                 |
| ----- | ------ | ----------------------------------------------------------- |
| lat   | Float! | Latitude in decimal degrees. Valid range: \[-90.0, 90.0]    |
| lon   | Float! | Longitude in decimal degrees. Valid range: \[-180.0, 180.0] |

#### Promotions Input

Document promotion configuration for pinning specific documents to the top of search results.

#### Promotions Input

| Field     | Type    | Description                                                    |
| --------- | ------- | -------------------------------------------------------------- |
| documents | \[ID!]! | Array of document IDs to promote to the top of search results. |

> **Note**
>
> **Format Requirements:**
>
> - Array can be empty — an empty array `[]` is treated as a no-op
> - Each ID must be a non-empty string
> - IDs cannot contain: `/` `\` `?` `#` `.` or spaces
> - Common convention is `type:id` format (e.g., `post:123`, `product:abc-456`)
> - Maximum 100 documents allowed per query
> - Duplicate IDs are deduplicated (first occurrence is kept)
>
> **Behavior:**
>
> - Promoted documents appear at the top in the exact order specified
> - Promoted documents ignore sorting and filtering
> - Non-existent document IDs are silently ignored
>
> **Example:** `["post:123", "product:456", "page:789"]`

#### Custom Results Input

Query-specific custom results configuration for pinning specific documents to the top of search results when users search for particular terms.

| Field     | Type    | Description                                           |
| --------- | ------- | ----------------------------------------------------- |
| query     | String! | Required search query to match against user searches. |
| documents | \[ID!]! | Array of document IDs to pin when the query matches.  |

#### TruncateFieldInput

Input for truncating long text fields in search results to reduce payload size.

| Field     | Type    | Description                                                                             |
| --------- | ------- | --------------------------------------------------------------------------------------- |
| field     | String! | Field name to truncate (e.g., “post_content”, “post_excerpt”).                          |
| maxLength | Int     | Maximum character length. Defaults to 200. Truncated strings append “…” (3 characters). |

> **Note**
>
> **`query` field:**
>
> - Case-insensitive exact match (whitespace trimmed)
> - No partial matching — `"cucumber"` does NOT match `"cucumbers"` or `"cucumber salad"`
> - Cannot be empty or consist only of whitespace
>
> **`documents` field:**
>
> - An empty array `[]` is treated as a no-op (no documents will be pinned)
> - Maximum 100 documents per configuration
> - Custom result documents appear after promotions but before organic results
> - Ignore sorting and filtering
> - Non-existent IDs are silently ignored
>
> **Request limit:** Maximum 100 configurations per request.

#### SearchResult Type

| Field     | Type              | Description                                |
| --------- | ----------------- | ------------------------------------------ |
| total     | Int               | The total number of documents returned.    |
| documents | \[SearchDocument] | The list of documents matching the search. |

#### SearchDocument Type

| Field | Type      | Description                                                                                       |
| ----- | --------- | ------------------------------------------------------------------------------------------------- |
| id    | ID!       | The Search ID of the document.                                                                    |
| score | Float     | The Search score of the document.                                                                 |
| sort  | \[String] | Values used to sort documents. Can be used in combination with searchAfter for cursor pagination. |
| data  | Map!      | The document data.                                                                                |

### Enumerations

#### SearchOptionEnum

| Name     | Description                 |
| -------- | --------------------------- |
| fuzzy    | The fuzzy search option.    |
| stemming | The stemming search option. |

#### OrderByDirection

| Name | Description               |
| ---- | ------------------------- |
| asc  | Sort in ascending order.  |
| desc | Sort in descending order. |

#### SEMANTIC_SEARCH_TYPE

| Value | Description       |
| ----- | ----------------- |
| BASIC | Basic Search type |

### Scalars

#### Distance

Distance value with unit. Format: `<number><unit>`

**Supported units:**

- `km` (kilometers) - e.g., `"5km"`
- `mi` (miles) - e.g., `"10.5mi"`
- `m` (meters) - e.g., `"1000m"`
- `ft` (feet) - e.g., `"500ft"`
- `yd` (yards) - e.g., `"100yd"`

**Examples:** `"5km"`, `"10.5mi"`, `"1000m"`

**Notes:**

- Negative values are not allowed
- Must be positive numeric values
- Unit is required

## Was this helpful?

Thanks for the feedback!

```json
{"@context":"https://schema.org","@graph":[{"@type":"WebSite","@id":"https://developers.wpengine.com/#website","name":"WP Engine Product Documentation","url":"https://developers.wpengine.com/","description":"Resources for developing, deploying, and scaling on WP Engine.","inLanguage":"en-US","keywords":["API documentation","product documentation","headless WordPress","managed WordPress hosting"],"publisher":{"@id":"https://wpengine.com/#organization"}}]}
```
