Skip to content
WP Engine Developers

Using the Find API

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.

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.

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

Section titled “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.

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 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.

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.

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

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

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

  • AND: Requires all terms to be present. Use the AND keyword.

    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.

    query: "comedy OR drama"

    Or using the | symbol:

    query: "comedy | drama"

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

    query: "comedy drama" // This is equivalent to "comedy | drama"
  • NOT: Excludes documents containing the term. Use the NOT keyword or - symbol.

    query: "sports NOT football"

    Or using the - symbol:

    query: "sports -football"
  • Required Term: Use + to require a specific term to be present.

    query: "+coach"

    This ensures “coach” must be in the results.

You can combine these operators for complex queries:

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

Use wildcards for pattern matching.

  • Wildcards: Use ? for single character and * for multiple characters.
    query: "qu?ck bro*"
    This matches “quick brown”, “quack brown”, “quick bronze”, etc.
  • Exact Phrases: Use double quotes for exact phrase matching.
    query: "\"quick brown fox\""

Use parentheses to control operator precedence and create complex queries.

query: "(quick | fast) AND fox"

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

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

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

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.

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”.

You can target a filter to a specific field.

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

Combine multiple filter conditions using boolean operators.

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:

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

Use wildcards in filter expressions for pattern matching.

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

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

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}.

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:

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

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

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

This finds documents where comment_count is 10 or greater.

For simpler range queries, you can use comparison operators:

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

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

To combine upper and lower bounds:

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

Filter by date ranges using the same syntax:

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:

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:

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

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

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

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

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

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

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

Use offset and limit for simple pagination.

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

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.

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:

{
"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.

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
}
}
}

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

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

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

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

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
}
}
}

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.

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”.

Boost the relevance of more recent documents using timeDecay.

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

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

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

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.

  • 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
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”.

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.

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.

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):

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:

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

Second page (using searchAfter - no promoted documents):

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).

The API enforces strict validation rules for promoted documents:

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

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.

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

See the Null/Nil documents field 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.

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.

Null/Nil documents field:

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

Invalid document ID format:

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

Duplicate document ID:

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

Too many documents:

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

Empty ID string:

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

Seasonal Campaign with Semantic Search

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

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:

# 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:

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:

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
}
}
}
}
}

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
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
}
}
}

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.

  • 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
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.

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

Multiple Configurations for the Same Query

Section titled “Multiple Configurations for the Same Query”

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

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

Section titled “How Custom Results Work with Other Parameters”

With Promotions

Promotions always appear first, followed by custom results:

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:

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:

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:

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”).

The API enforces strict validation rules for custom results:

Configuration Limits

Common Error Responses

Too Many Configurations:

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

Nil Configuration:

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

Empty Query:

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

Too Many Documents:

{
"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:

{
"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
}

Multi-Query Strategy

Configure different results for different search terms:

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:

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:

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:

# 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
}
}
}

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
FeaturePromotionsCustom Results
When they appearEvery queryOnly when query matches
Use caseUniversal pinning (e.g., site-wide announcements)Query-specific recommendations
ConfigurationSingle list of documentsMultiple query → document mappings
PriorityHighest (always first)Second (after promotions)
ComplexitySimpleMore 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

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

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

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

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.

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

Get a count of unique terms in a field.

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.

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

Group documents into numeric or date ranges.

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

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.
query FieldSelection {
find(
query: "blog posts"
options: {
includeFields: ["post_title", "post_excerpt", "post_date", "author"]
}
) {
documents {
id
data
}
}
}

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

FieldDescription
findSearches for documents in an index based on specific parameters.
ParameterTypeDescription
queryString!The search query string, supporting Simple Query String syntax for full-text search. This is required.
filterStringA strict filter string (using Query String syntax) to narrow down the documents to be searched.
geoConstraintsGeoConstraintsInputMultiple 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.
offsetIntThe number of results to skip for offset-based pagination.
limitIntThe maximum number of results to return.
fields[SearchField]A global list of fields to search in, with optional weights. Overridden by options.fields.
toleranceSearchOptionThe search tolerance, such as fuzzy or stemming. Defaults to stemming.
metaMetaInputOptional metadata for logging purposes.
aggregateAggregateInputDefines aggregations (facets) to be computed on the result set.
semanticSearchSemanticSearchInputConfiguration for performing semantic or hybrid search.
optionsOptionsInputAdditional options for controlling search behavior, like type-specific fields, time decay, and document promotions.
FieldTypeDescription
nameString!The field name to search for in the document.
weightIntThe weight of the field, affecting the order of returned documents.

Optional meta data input for logging

FieldTypeDescription
actionStringPerformed action e.g. index
systemStringThe requester system name
sourceStringThe requester hostname
FieldTypeDescription
terms[TermAggregateInput]Aggregation based on terms.
ranges[RangeAggregateInput]Aggregation based on ranges.
FieldTypeDescription
fieldString!Field name we want to aggregate on
sizeIntTo retrieve additional terms, employ the “size” parameter. By default, the terms aggregation fetches the top ten terms with the highest document counts.
minCountIntIf minCount is zero, zero count results are returned (else we omit them)
FieldTypeDescription
fieldString!Field name we want to aggregate on
ranges[RangeInput]Range Input options
sizeIntTo retrieve additional terms, employ the “size” parameter. By default, the terms aggregation fetches the top ten terms with the highest document counts.

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

FieldTypeDescription
toFloatFrom value (Inclusive)
fromFloatTo value (Exclusive)
FieldTypeDescription
fieldString!The field to order the documents by.
directionOrderByDirectionThe sort direction (asc or desc).
unmappedTypeStringDeprecated: Going to be removed soon. When its present default weight score is applied for ordering
FieldTypeDescription
nameSearchOptionEnum!The search option name (fuzzy or stemming).
fuzzyDistanceIntOptional 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.

Semantic Search query input

FieldTypeDescription
searchBiasInt!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
typeSEMANTIC_SEARCH_TYPESemantic Search type

Additional options for controlling search behavior and result formatting

FieldTypeDescription
fieldsTypeFieldsType-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.
queryRescorerQueryRescorerInputQuery rescorer configuration for improving search precision by re-scoring top documents.
promotionsPromotionsOptional 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.

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

FieldTypeDescription
typeFieldNameStringThe name of the field that contains the type information. Defaults to “post_type”.
types[Type!]!List of type-specific field configurations.

Defines which fields to search for a specific document type.

FieldTypeDescription
typeString!The type name (e.g., “post”, “page”, “product”).
fields[SearchField!]!The fields to search for this specific type, with optional weights.

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

FieldTypeDescription
fieldStringThe field to apply the decay function to. Defaults to ‘post_date’ if not specified.
scaleString!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.
decayRateFloatThe score multiplier at the ‘scale’ distance. Defaults to 0.25 if not provided. Value should be between 0 and 1.
offsetStringSets a time offset from ‘now’ before the decay begins. Defaults to ‘7d’. Example: ‘7d’. Uses same format as scale.

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)
FieldTypeDescription
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.

Circular area constraint defined by center point and maximum distance.

FieldTypeDescription
centerGeoPointInput!Center point of the circular area
maxDistanceDistance!Maximum distance from center point

Bounding box rectangular area defined by southwest and northeast corners.

FieldTypeDescription
southwestGeoPointInput!Southwest corner of the bounding box. This is the minimum latitude and longitude point.
northeastGeoPointInput!Northeast corner of the bounding box. This is the maximum latitude and longitude point.

Geographic coordinate input

FieldTypeDescription
latFloat!Latitude in decimal degrees. Valid range: [-90.0, 90.0]
lonFloat!Longitude in decimal degrees. Valid range: [-180.0, 180.0]

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

FieldTypeDescription
documents[ID!]!Array of document IDs to promote to the top of search results.

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

FieldTypeDescription
queryString!Required search query to match against user searches.
documents[ID!]!Array of document IDs to pin when the query matches.
FieldTypeDescription
totalIntThe total number of documents returned.
documents[SearchDocument]The list of documents matching the search.
FieldTypeDescription
idID!The Search ID of the document.
scoreFloatThe Search score of the document.
sort[String]Values used to sort documents. Can be used in combination with searchAfter for cursor pagination.
dataMap!The document data.
NameDescription
fuzzyThe fuzzy search option.
stemmingThe stemming search option.
NameDescription
ascSort in ascending order.
descSort in descending order.
ValueDescription
BASICBasic Search type

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