Search and filters

Learn how to use search endpoints and construct filters to query data.

Overview

Many Endear resources can be queried through search* endpoints that support full-text search, sorting, pagination, and filtering. All search endpoints return cursor-based paginated connections (see GraphQL pagination).

Basic search query

Each search endpoint accepts a search argument for full-text search, along with sortBy, sortDir, and pagination arguments. The sortBy value is a GraphQL enum specific to each endpoint (e.g. SortAppointmentEventsBy, SortMessagesBy):

query {
  searchAppointmentEvents(
    first: 10
    sortBy: CREATED_AT
    sortDir: DESC
    search: "john"
  ) {
    edges {
      node {
        id
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Discovering available filters

Use the filterDefinitions query to discover which filter keys are available for a given search endpoint and what type each filter expects:

query {
  filterDefinitions(context: APPOINTMENT_EVENTS) {
    key
    type
  }
}

This returns a list of filter definitions:

{
  "data": {
    "filterDefinitions": [
      { "key": "eventStatus", "type": "STRING" },
      { "key": "bookerEmail", "type": "STRING" },
      { "key": "eventStartsAt", "type": "ABSOLUTE_DATE" },
      { "key": "isDeleted", "type": "BOOLEAN" },
      { "key": "rescheduledCount", "type": "NUMBER" }
    ]
  }
}

The context argument accepts the following values:

ContextCorresponding search endpoint
APPOINTMENT_EVENTSsearchAppointmentEvents
CONVERSATIONSsearchConversations
DRAFTSsearchDrafts
MARKETING_EVENTSsearchMarketingEvents
MARKETING_SUBSCRIPTIONSsearchMarketingSubscriptions
MESSAGESsearchMessages
NOTESsearchNotes
ORDER_ATTRIBUTIONSsearchOrderAttributions
STORIESsearchStories
TEMPLATESsearchTemplates

Constructing filters

Filters are passed through the filters argument as a list of SearchFilterInput objects. Each object in the list is ANDed together, allowing you to combine multiple filter groups.

Each individual SearchFilterInput contains either allOf (AND logic) or anyOf (OR logic), but not both. To combine AND and OR logic, use separate objects in the filters list.

Each SearchFilterEntryInput has a key (the filter key from filterDefinitions) and exactly one filter field matching the filter type. Providing zero or more than one filter field in a single entry will result in an INVALID_FILTERS error.

Filter type mapping

The type returned by filterDefinitions tells you which field to use on SearchFilterEntryInput:

FilterDefinitionTypeSearchFilterEntryInput field
STRINGstringFilter
BOOLEANbooleanFilter
ABSOLUTE_DATEabsoluteDateFilter
NUMBERnumberFilter
MONEYmoneyFilter
RECURRING_DATErecurringDateFilter
LOCATIONlocationFilter

String filters

Use stringFilter for STRING type filters. Provide exactly one of the following operators:

OperatorDescription
isAnyOfMatches any of the provided values
isAllOfMatches all of the provided values
isNoneOfMatches none of the provided values
containsAnyOfContains any of the provided substrings
containsAllOfContains all of the provided substrings
containsNoneOfContains none of the provided substrings
startsWithAnyOfStarts with any of the provided prefixes
startsWithAllOfStarts with all of the provided prefixes
startsWithNoneOfStarts with none of the provided prefixes
endsWithAnyOfEnds with any of the provided suffixes
endsWithAllOfEnds with all of the provided suffixes
endsWithNoneOfEnds with none of the provided suffixes
isKnowntrue if the field has a value, false if it does not

Boolean filters

Use booleanFilter for BOOLEAN type filters. Provide exactly one operator:

OperatorDescription
isAnyOfMatches any of the provided boolean values
isKnowntrue if the field has a value, false if it does not

Absolute date filters

Use absoluteDateFilter for ABSOLUTE_DATE type filters. The outer input accepts isAnyOf, isAllOf, or isKnown. Each date term accepts exactly one of:

OperatorDescription
betweenA list of two DateTime values defining a range
moreThanOrEqualToA DateTime lower bound
lessThanOrEqualToA DateTime upper bound
isKnowntrue if the field has a value, false if it does not

Number filters

Use numberFilter for NUMBER type filters. The outer input accepts isAnyOf, isAllOf, or isKnown. Each number term accepts exactly one of:

OperatorDescription
equalsMatches the exact value
betweenA list of two numbers defining a range
moreThanOrEqualToA numeric lower bound
lessThanOrEqualToA numeric upper bound
isKnowntrue if the field has a value, false if it does not

Examples

Single filter (AND)

Find appointment events with a specific status:

query {
  searchAppointmentEvents(
    first: 10
    filters: [
      {
        allOf: [
          { key: "eventStatus", stringFilter: { isAnyOf: ["confirmed", "pending"] } }
        ]
      }
    ]
  ) {
    edges {
      node {
        id
      }
    }
  }
}

Multiple filters (AND)

Find confirmed appointment events starting after a specific date:

query {
  searchAppointmentEvents(
    first: 10
    filters: [
      {
        allOf: [
          { key: "eventStatus", stringFilter: { isAnyOf: ["confirmed"] } }
          { key: "eventStartsAt", absoluteDateFilter: { isAnyOf: [{ moreThanOrEqualTo: "2025-01-01T00:00:00Z" }] } }
        ]
      }
    ]
  ) {
    edges {
      node {
        id
      }
    }
  }
}

OR filters

Find appointment events that are either confirmed or rescheduled:

query {
  searchAppointmentEvents(
    first: 10
    filters: [
      {
        anyOf: [
          { key: "eventStatus", stringFilter: { isAnyOf: ["confirmed"] } }
          { key: "isRescheduled", booleanFilter: { isAnyOf: [true] } }
        ]
      }
    ]
  ) {
    edges {
      node {
        id
      }
    }
  }
}

Combining AND and OR groups

You can pass multiple SearchFilterInput objects in the filters list. Each object is ANDed together, while entries within anyOf are ORed.

This example finds non-deleted appointment events where either the status is "confirmed" or the event has been rescheduled:

query {
  searchAppointmentEvents(
    first: 10
    filters: [
      {
        allOf: [
          { key: "isDeleted", booleanFilter: { isAnyOf: [false] } }
        ]
      }
      {
        anyOf: [
          { key: "eventStatus", stringFilter: { isAnyOf: ["confirmed"] } }
          { key: "isRescheduled", booleanFilter: { isAnyOf: [true] } }
        ]
      }
    ]
  ) {
    edges {
      node {
        id
      }
    }
  }
}

Checking if a field exists

Use isKnown to filter by whether a field has a value:

query {
  searchMessages(
    first: 10
    filters: [
      {
        allOf: [
          { key: "sentAt", absoluteDateFilter: { isKnown: true } }
        ]
      }
    ]
  ) {
    edges {
      node {
        id
      }
    }
  }
}