Skip to main content
The search query is where personalization happens. Pass in a guest’s dietary restrictions, allergen exclusions, and nutritional preferences—we traverse our entire ingredient hierarchy to find dishes that are safe and satisfying for them. Results come back grouped by match quality: full matches, almost matches (with explanations), and non-matches. This gives you everything you need to build interfaces that help guests find exactly what they’re looking for—while being transparent about dishes that come close but don’t quite fit.
This query requires an API key scoped to your chain in the Authorization header. Optionally include X-Session-ID for analytics and personalization; you do not need to pass chain or session in the GraphQL query itself.

Query

query SmartMenuSearch(
  $restaurantId: ID
  $preferences: PreferencesInput
  $searchTerm: String
  $category: String
  $pagination: PaginationArgs
) {
  search(
    restaurantId: $restaurantId
    preferences: $preferences
    searchTerm: $searchTerm
    category: $category
    pagination: $pagination
  ) {
    matches {
      dish { ...DishFields }
      matchStatus
    }
    almostMatches {
      dish { ...DishFields }
      matchStatus
      matchReasons
    }
    notMatches {
      dish { ...DishFields }
      matchStatus
      matchReasons
    }
    counts {
      matches
      almostMatches
      notMatches
      total
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
    }
  }
}

Parameters

All parameters are optional. Chain context comes from your Authorization header; restaurant and session context are provided via restaurantId (argument) and X-Session-ID (header).
ParameterTypeDescription
restaurantIdIDScope search to a single restaurant. If omitted, the search runs in the chain’s widget scope (all widget dishes).
preferencesPreferencesInputDietary filters (see below)
searchTermStringText search (dish name, description)
categoryStringFilter by menu category
paginationPaginationArgsPagination controls

PreferencesInput

input PreferencesInput {
  diets: [DietType!]
  excludeAllergens: [AllergenType!]
  calorieRange: RangeInput
  nutrientRanges: NutrientRangesInput
}

input RangeInput {
  min: Int
  max: Int
}

input NutrientRangesInput {
  protein: RangeInput
  carbohydrates: RangeInput
  fatTotal: RangeInput
}

enum DietType {
  VEGAN
  VEGETARIAN
  PESCATARIAN
}

enum AllergenType {
  DAIRY
  EGG
  FISH
  SHELLFISH
  TREE_NUT
  PEANUT
  WHEAT
  SOY
  SESAME
}

PaginationArgs

input PaginationArgs {
  first: Int       # Number of results when paginating forwards
  after: String    # Cursor for next page (forward pagination)
  last: Int        # Number of results when paginating backwards
  before: String   # Cursor for previous page (backward pagination)
}
Pagination uses opaque, base64-encoded page-number cursors:
  • Forward pagination (first / after): When after is omitted, the resolver returns the first page. When present, after is treated as the cursor for the current page and the resolver returns the next page.
  • Backward pagination (last / before): When both are present, before is treated as the cursor for the current page and the resolver returns the previous page. When only last is provided, the resolver returns the last page.
  • Cursors should be treated as opaque strings; clients should not rely on the encoding details.

Response

Match Groups

Results are grouped into three categories:
GroupDescriptionUse Case
matchesFully meets all preferencesDisplay prominently
almostMatchesPartial match with exceptionsDisplay with warnings
notMatchesDoes not meet critical preferencesDisplay greyed out or hide

MatchStatus Enum

enum MatchStatus {
  MATCH           # Fully matches all preferences
  ALMOST_MATCH    # Partial match, has exceptions
  NOT_MATCH       # Does not meet preferences
}

Match Reasons

For almostMatches and notMatches, the matchReasons array explains why:
{
  "matchReasons": [
    "Contains Dairy",
    "Exceeds calorie limit (650 > 600)"
  ]
}

Example

query SmartMenuSearch {
  search(
    preferences: {
      diets: [VEGETARIAN]
      excludeAllergens: [PEANUT]
      calorieRange: { max: 600 }
    }
  ) {
    matches {
      dish {
        id
        name
        nutrition { calories, protein }
      }
      matchStatus
    }
    almostMatches {
      dish { id, name }
      matchReasons
    }
    counts {
      matches
      almostMatches
      notMatches
      total
    }
  }
}

Examples

Basic Search (No Filters)

query {
  search {
    matches {
      dish {
        id
        name
        nutrition { calories }
      }
    }
    counts { total }
  }
}
{
  "data": {
    "search": {
      "matches": [
        { "dish": { "id": "dish_001", "name": "Garden Salad", "nutrition": { "calories": 320 } } },
        { "dish": { "id": "dish_002", "name": "Mediterranean Bowl", "nutrition": { "calories": 450 } } }
      ],
      "counts": { "total": 24 }
    }
  }
}

Search with Dietary Preferences

query VegetarianSearch {
  search(
    preferences: {
      diets: [VEGETARIAN]
      excludeAllergens: [PEANUT, TREE_NUT]
      calorieRange: { max: 600 }
    }
  ) {
    matches {
      dish {
        id
        name
        category
        nutrition { calories, protein }
        diets { type }
      }
      matchStatus
    }
    almostMatches {
      dish { id, name }
      matchReasons
    }
    counts {
      matches
      almostMatches
      notMatches
    }
  }
}
{
  "data": {
    "search": {
      "matches": [
        {
          "dish": { "id": "dish_001", "name": "Garden Salad", "category": "Salads", "nutrition": { "calories": 320, "protein": 12 }, "diets": [{ "type": "VEGETARIAN" }] },
          "matchStatus": "MATCH"
        }
      ],
      "almostMatches": [
        { "dish": { "id": "dish_003", "name": "Caesar Salad" }, "matchReasons": ["Contains Dairy"] }
      ],
      "counts": { "matches": 8, "almostMatches": 4, "notMatches": 12 }
    }
  }
}

Text Search with Category Filter

query SearchSalads {
  search(
    searchTerm: "chicken"
    category: "Salads"
    pagination: { first: 10 }
  ) {
    matches {
      dish {
        id
        name
        description
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
{
  "data": {
    "search": {
      "matches": [
        { "dish": { "id": "dish_005", "name": "Grilled Chicken Salad", "description": "Mixed greens with grilled chicken breast" } },
        { "dish": { "id": "dish_006", "name": "Chicken Caesar Salad", "description": "Romaine, parmesan, croutons, grilled chicken" } }
      ],
      "pageInfo": { "hasNextPage": false, "endCursor": "cursor_abc123" }
    }
  }
}

Paginated Results

# First page
query Page1 {
  search(pagination: { first: 20 }) {
    matches { dish { id, name } }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

# Next page (use endCursor from previous response)
query Page2 {
  search(pagination: { first: 20, after: "eyJpZCI6MjB9" }) {
    matches { dish { id, name } }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Full Example Response

{
  "data": {
    "search": {
      "matches": [
        {
          "dish": {
            "id": "dish_001",
            "partnerDishId": "olo_12345",
            "name": "Garden Salad",
            "category": "Salads",
            "imageUrl": "https://cdn.example.com/garden-salad.jpg",
            "nutrition": {
              "calories": 320,
              "protein": 12,
              "carbohydrates": 28,
              "fatTotal": 15
            },
            "allergens": [],
            "diets": [
              { "type": "VEGAN", "displayName": "Vegan" },
              { "type": "VEGETARIAN", "displayName": "Vegetarian" }
            ]
          },
          "matchStatus": "MATCH"
        },
        {
          "dish": {
            "id": "dish_004",
            "partnerDishId": "olo_12348",
            "name": "Mediterranean Bowl",
            "category": "Bowls",
            "imageUrl": "https://cdn.example.com/med-bowl.jpg",
            "nutrition": {
              "calories": 450,
              "protein": 18,
              "carbohydrates": 52,
              "fatTotal": 20
            },
            "allergens": [],
            "diets": [
              { "type": "VEGETARIAN", "displayName": "Vegetarian" }
            ]
          },
          "matchStatus": "MATCH"
        }
      ],
      "almostMatches": [
        {
          "dish": {
            "id": "dish_002",
            "partnerDishId": "olo_12346",
            "name": "Caesar Salad",
            "category": "Salads",
            "imageUrl": "https://cdn.example.com/caesar-salad.jpg",
            "nutrition": {
              "calories": 450,
              "protein": 18,
              "carbohydrates": 25,
              "fatTotal": 28
            },
            "allergens": [
              { "type": "DAIRY", "displayName": "Dairy" }
            ],
            "diets": [
              { "type": "VEGETARIAN", "displayName": "Vegetarian" }
            ]
          },
          "matchStatus": "ALMOST_MATCH",
          "matchReasons": ["Contains Dairy"]
        }
      ],
      "notMatches": [
        {
          "dish": {
            "id": "dish_003",
            "partnerDishId": "olo_12347",
            "name": "Grilled Chicken Salad",
            "category": "Salads"
          },
          "matchStatus": "NOT_MATCH",
          "matchReasons": ["Contains Meat"]
        }
      ],
      "counts": {
        "matches": 8,
        "almostMatches": 4,
        "notMatches": 12,
        "total": 24
      },
      "pageInfo": {
        "hasNextPage": true,
        "hasPreviousPage": false,
        "startCursor": "eyJpZCI6MX0=",
        "endCursor": "eyJpZCI6MjR9"
      }
    }
  }
}