Skip to content

Rich Filters

FraiseQL’s rich filters provide sophisticated query operators for 49 semantic scalar types, enabling domain-specific filtering beyond basic string matching and numeric comparisons.

Rich filters are automatically-generated GraphQL query operators that specialize in semantic domains like emails, geographic locations, financial codes, and more. Each semantic type generates:

  • Standard operators: eq, neq, contains, isnull
  • Domain-specific operators: Email domain matching, geographic distance, financial validation, etc.

All operators compile to optimized SQL at build time — zero runtime overhead.

query {
# Find users at a specific company domain
users(where: { email: { domainEq: "example.com" } }) {
id
email
}
# Find restaurants within 5km
restaurants(where: {
location: {
distanceWithin: {
latitude: 40.7128
longitude: -74.0060
radiusKm: 5
}
}
}) {
name
}
}

FraiseQL supports 49 semantic scalar types organized into 9 categories:

Email · PhoneNumber · URL · DomainName · Hostname

IBAN · CUSIP · ISIN · SEDOL · LEI · MIC · CurrencyCode · Money · ExchangeCode · ExchangeRate · StockSymbol

PostalCode · Latitude · Longitude · Coordinates · Timezone · LocaleCode · LanguageCode · CountryCode

Slug · SemanticVersion · HashSHA256 · APIKey · LicensePlate · VIN · TrackingNumber · ContainerNumber

IPAddress · IPv4 · IPv6 · MACAddress · CIDR · Port

AirportCode · PortCode · FlightNumber

Markdown · HTML · MimeType · Color · Image · File

DateRange · Duration · Percentage

Plus additional types for UUIDs, hashes, encodings, and more.

See Semantic Scalars Reference for complete documentation of all 49 types.

Execute queries with rich type filters in Apollo Sandbox below:

Rich Filters Example

Try filtering by email domain, geographic proximity, VIN, or other semantic types. Modify the query to explore different operators!

Loading Apollo Sandbox...

This sandbox uses Apollo Sandbox (the same GraphQL IDE as fraiseql serve). Your queries execute against the endpoint below. No data is sent to Apollo. Learn more about privacy →

Find all users at a specific company:

query {
users(where: { email: { domainEq: "acme.com" } }) {
id
email
name
}
}

Generates efficient database-specific SQL:

SELECT data FROM v_user
WHERE SUBSTRING(data->>'email' FROM POSITION('@' IN data->>'email') + 1) = 'acme.com'

Find restaurants within 5 kilometers:

query {
restaurants(where: {
location: {
distanceWithin: {
latitude: 40.7128
longitude: -74.0060
radiusKm: 5
}
}
}) {
id
name
location { latitude longitude }
}
}

Works across all databases with database-specific optimizations:

SELECT data FROM v_restaurant
WHERE ST_DWithin(
ST_Point((data->'location'->>'longitude')::float, (data->'location'->>'latitude')::float)::geography,
ST_Point(-74.0060, 40.7128)::geography,
5000 -- 5km in meters
)

Query by international bank account number:

query {
accounts(where: {
iban: { ibanCountryEq: "DE" }
}) {
id
iban
balance
}
}

Find projects lasting at least 90 days:

query {
projects(where: {
timeline: {
durationGte: 90
overlaps: {
start: "2024-06-01T00:00:00Z"
end: "2024-08-31T23:59:59Z"
}
}
}) {
id
name
}
}

Find devices on a specific network:

query {
devices(where: {
ipAddress: { cidrContains: "192.168.0.0/24" }
}) {
id
hostname
ipAddress
}
}
TypePostgreSQLMySQLSQLiteSQL Server
Email✅ Native✅ Native✅ Native✅ Native
PhoneNumber✅ Regex✅ REGEXP✅ GLOB✅ LIKE
Coordinates✅ PostGIS✅ ST_Distance⚠️ Approx✅ Geography
DateRange✅ Intervals✅ DATEDIFF✅ julianday✅ DATEDIFF
Duration✅ INTERVAL✅ Parse✅ Parse✅ Parse

Geographic queries on PostgreSQL require the PostGIS extension:

-- Enable PostGIS
CREATE EXTENSION IF NOT EXISTS postgis;
-- Create spatial index for performance
CREATE INDEX idx_tv_restaurant_location ON tv_restaurant USING GIST ((data->'location'));

When you compile your schema with fraiseql compile:

@fraiseql.type
class User:
email: Email
location: Coordinates

FraiseQL automatically generates:

input EmailWhereInput {
eq: String
neq: String
contains: String
domainEq: String
domainIn: [String!]
domainEndswith: String
}

The compiler embeds database-specific SQL templates in the compiled schema:

{
"operators": {
"domainEq": {
"postgres": "SUBSTRING(email FROM POSITION('@' IN email) + 1) = $1",
"mysql": "SUBSTRING_INDEX(email, '@', -1) = %s",
"sqlite": "SUBSTR(email, INSTR(email, '@') + 1) = ?",
"sqlserver": "SUBSTRING(email, CHARINDEX('@', email) + 1, LEN(email)) = @p1"
}
}
}

Rich filters are resolved at compile time, not runtime:

  • ✅ No reflection or dynamic generation
  • ✅ No string parsing
  • ✅ No database queries for metadata
  • ✅ All operators pre-compiled

Reference data is embedded in schema.compiled.json:

{
"lookup_data": {
"countries": {
"US": { "name": "United States", "continent": "North America" },
"FR": { "name": "France", "continent": "Europe" }
},
"currencies": {
"USD": { "symbol": "$", "decimal_places": 2 },
"EUR": { "symbol": "€", "decimal_places": 2 }
}
}
}

This ensures:

  • Fast lookups with no database queries
  • Consistent validation across instances
  • Deterministic compilation

Rich filters pre-validate parameters before SQL execution. Validation rules for each operator are compiled into the Rust runtime — for example, domainEq validates that the value is a well-formed domain name, ibanCountryEq validates the MOD-97 checksum, and distanceWithin enforces that radiusKm is a positive number. These rules are not user-configurable via TOML.

Rich filters generate optimized SQL:

  1. Index-friendly: Use GIST/BRIN indexes for geospatial queries
  2. String-optimized: Prefix indexes for domain matching
  3. Cached lookups: Reference data cached in schema, not queried

This query:

users(where: { email: { domainEq: "example.com" } })

Becomes a simple string comparison with good index support:

WHERE SUBSTRING(...) = 'example.com'

This query:

restaurants(where: {
location: { distanceWithin: { latitude: 40.7128, longitude: -74.0060, radiusKm: 5 } }
})

Uses PostGIS efficiently:

WHERE ST_DWithin(location::geography, ST_Point(...)::geography, 5000)

Rich filter operators are available via REST query parameters using bracket notation. Each operator maps directly from the GraphQL where input to a ?field[operator]=value query parameter.

GraphQL filterREST bracket notation
email: { domainEq: "example.com" }?email[domainEq]=example.com
email: { domainIn: ["a.com", "b.com"] }?email[domainIn]=a.com,b.com
iban: { ibanCountryEq: "DE" }?iban[ibanCountryEq]=DE
ipAddress: { cidrContains: "192.168.0.0/24" }?ipAddress[cidrContains]=192.168.0.0/24
name: { icontains: "Ali" }?name[icontains]=Ali
age: { gte: 18 }?age[gte]=18

For complex queries combining multiple rich filter operators, use the ?filter= JSON parameter which accepts the full FraiseQL where DSL:

GET /rest/v1/users?filter={"email":{"domainEq":"example.com"},"location":{"distanceWithin":{"latitude":40.7128,"longitude":-74.006,"radiusKm":5}}}

Operators that accept structured input (like distanceWithin) are only available via the ?filter= JSON DSL, not via bracket notation.

See REST Transport for the full list of bracket operators and the JSON filter escape hatch.

Rich filters are also available via gRPC request fields. Filters defined in your schema translate directly to protobuf request message fields for gRPC.