The expr() function creates type-safe SurrealQL expressions using standalone operator functions, providing an alternative to writing raw SurrealQL strings.
Import:
import {
expr,
eq, eeq, ne,
gt, gte, lt, lte,
and, or, not,
contains, containsAny, containsAll, containsNone,
inside, outside, intersects,
matches, knn,
between,
raw
} from 'surrealdb';Source: utils/expr.ts
Function signature
function expr(expression: ExprLike): BoundQueryParameters
Parameter | Type | Description |
|---|---|---|
expression | ExprLike | An expression created using operator functions. |
Returns
BoundQuery - Compiled query with bindings
Comparison operators
eq(field, value)
Equality comparison (=).
const adults = expr(eq('age', 18));
await db.select(new Table('users')).where(adults);
// WHERE age = 18 eeq(field, value)
Exact equality comparison (==).
const exact = expr(eeq('count', 0));
// WHERE count == 0 ne(field, value)
Not equal comparison (!=).
const notAdmin = expr(ne('role', 'admin'));
// WHERE role != 'admin' gt(field, value), gte(field, value)
Greater than (>) and greater than or equal (>=).
const adults = expr(gt('age', 17));
const adultsInclusive = expr(gte('age', 18)); lt(field, value), lte(field, value)
Less than (<) and less than or equal (<=).
const young = expr(lt('age', 30));
const youngInclusive = expr(lte('age', 29));Logical operators
and(...conditions)
Logical AND - all conditions must be true.
const premiumAdults = expr(and(
eq('tier', 'premium'),
gte('age', 18)
));
// WHERE tier = 'premium' AND age >= 18 or(...conditions)
Logical OR - at least one condition must be true.
const adminOrModerator = expr(or(
eq('role', 'admin'),
eq('role', 'moderator')
));
// WHERE role = 'admin' OR role = 'moderator' not(condition)
Logical NOT - inverts the condition.
const notBanned = expr(not(eq('status', 'banned')));
// WHERE NOT status = 'banned'Collection operators
contains(field, value)
Check if field contains value (CONTAINS).
const hasTag = expr(contains('tags', 'featured'));
// WHERE tags CONTAINS 'featured' containsAny(field, values)
Check if field contains any of the values (CONTAINSANY).
const hasAnyTag = expr(containsAny('tags', ['new', 'featured', 'trending']));
// WHERE tags CONTAINSANY ['new', 'featured', 'trending'] containsAll(field, values)
Check if field contains all values (CONTAINSALL).
const hasAllTags = expr(containsAll('tags', ['verified', 'premium']));
// WHERE tags CONTAINSALL ['verified', 'premium'] containsNone(field, values)
Check if field contains none of the values (CONTAINSNONE).
const noBadTags = expr(containsNone('tags', ['spam', 'banned']));
// WHERE tags CONTAINSNONE ['spam', 'banned']Geometry operators
inside(field, geometry)
Check if geometry is inside another (INSIDE).
const inRegion = expr(inside('location', regionPolygon));
// WHERE location INSIDE $regionPolygon outside(field, geometry)
Check if geometry is outside another (OUTSIDE).
const outsideZone = expr(outside('location', restrictedZone));
// WHERE location OUTSIDE $restrictedZone intersects(field, geometry)
Check if geometries intersect (INTERSECTS).
const overlaps = expr(intersects('area', otherArea));
// WHERE area INTERSECTS $otherAreaSearch operators
matches(field, query, ref?)
Full-text search match (@@ or @ref@).
// Basic match
const searchResults = expr(matches('content', 'searchTerm'));
// WHERE content @@ 'searchTerm'
// With reference number
const searchWithRef = expr(matches('content', 'searchTerm', 1));
// WHERE content @1@ 'searchTerm' knn(field, value, neighbors, metricOrEf?)
K-nearest neighbors vector search.
const similar = expr(knn('embedding', [0.1, 0.2, 0.3], 10, 'cosine'));
// WHERE embedding <|10,COSINE|> [0.1, 0.2, 0.3]Range operator
between(field, a, b)
Range check — shortcut for and(gte(field, a), lte(field, b)).
const midRange = expr(between('price', 10, 50));
// WHERE price >= 10 AND price <= 50Raw expressions
raw(sql)
Create raw SurrealQL expressions.
Only use raw() when no other operator is applicable. Incorrect use risks SQL injection.
const custom = expr(raw('custom_function()'));Complete examples
Basic filtering
import { expr, eq, gte } from 'surrealdb';
// Single condition
const active = expr(eq('status', 'active'));
const users = await db.select(new Table('users')).where(active);
// Multiple conditions with AND
const premiumAdults = expr(and(
eq('tier', 'premium'),
gte('age', 18),
eq('active', true)
));
const results = await db.select(new Table('users')).where(premiumAdults);Complex conditions
// Nested OR and AND
const eligibleUsers = expr(or(
and(
eq('tier', 'premium'),
gte('age', 18)
),
and(
eq('role', 'admin'),
eq('verified', true)
)
));
const users = await db.select(new Table('users')).where(eligibleUsers);Date filtering
import { DateTime, Duration } from 'surrealdb';
const cutoffDate = DateTime.now().minus(Duration.parse('30d'));
const recentUsers = expr(gte('created_at', cutoffDate));
const users = await db.select(new Table('users')).where(recentUsers);Array operations
// Check if user has specific tags
const hasFeaturedTag = expr(contains('tags', 'featured'));
// Check if has any of these tags
const hasPromotedTags = expr(containsAny('tags', ['featured', 'trending', 'new']));
// Must have all required tags
const fullyVerified = expr(containsAll('badges', ['email-verified', 'phone-verified']));
// Must not have any bad tags
const cleanContent = expr(containsNone('flags', ['spam', 'inappropriate']));Geospatial queries
import { GeometryPoint } from 'surrealdb';
const searchArea = new GeometryPolygon([/* ... */]);
// Find locations inside area
const nearby = expr(inside('location', searchArea));
const locations = await db.select(new Table('stores')).where(nearby);
// Find areas that intersect
const overlapping = expr(intersects('coverage_area', searchArea));
const zones = await db.select(new Table('zones')).where(overlapping);Full-text search
// Basic text search
const searchQuery = 'javascript tutorial';
const articles = await db.select(new Table('articles'))
.where(expr(matches('content', searchQuery)));
// With reference number for multi-field search
const multiField = expr(or(
matches('title', searchQuery, 1),
matches('content', searchQuery, 1)
));Vector search
// Find similar items using KNN
const queryVector = [0.1, 0.2, 0.3, /* ... */];
const similar = expr(knn('embedding', queryVector, 10, 'cosine'));
const results = await db.select(new Table('items')).where(similar);Reusable expressions
// Define reusable filters
const activeFilter = expr(eq('active', true));
const verifiedFilter = expr(eq('verified', true));
const premiumFilter = expr(eq('tier', 'premium'));
// Combine as needed
const premiumActive = expr(and(activeFilter, premiumFilter));
const verifiedActive = expr(and(activeFilter, verifiedFilter));
// Use in queries
const users1 = await db.select(new Table('users')).where(premiumActive);
const users2 = await db.select(new Table('users')).where(verifiedActive);Update with expressions
const condition = expr(and(
eq('status', 'pending'),
lt('created_at', DateTime.now().minus(Duration.parse('1h')))
));
const updated = await db.update(new Table('orders'))
.merge({ status: 'expired' })
.where(condition);Delete with expressions
const oldInactive = expr(and(
eq('active', false),
lt('last_login', DateTime.now().minus(Duration.parse('90d')))
));
const deleted = await db.delete(new Table('users')).where(oldInactive);Best practices
1. Use expressions for complex conditions
// Good: Type-safe and reusable
const condition = expr(and(
gte('age', 18),
eq('verified', true)
));
// Avoid: Raw strings (no type safety)
const condition = 'age >= 18 AND verified = true';2. Build expressions compositionally
// Good: Compose small expressions
const isAdult = expr(gte('age', 18));
const isVerified = expr(eq('verified', true));
const isActive = expr(eq('active', true));
const eligibleUsers = expr(and(isAdult, isVerified, isActive));
// You can reuse components
const premiumEligible = expr(and(isAdult, isVerified)); 3. Avoid raw() when possible
// Good: Use typed operators
const condition = expr(gte('score', 80));
// Avoid: Raw SQL (SQL injection risk)
const condition = expr(raw(`score >= ${userInput}`));4. Parameterise dynamic values
// Good: Values are automatically parameterised
const minAge = getUserInput();
const condition = expr(gte('age', minAge));
// Safe: minAge is bound as a parameter, not concatenatedCommon patterns
Dynamic filter builder
function buildUserFilter(options: {
minAge?: number;
tier?: string;
active?: boolean;
}) {
const conditions: ExprLike[] = [];
if (options.minAge !== undefined) {
conditions.push(gte('age', options.minAge));
}
if (options.tier) {
conditions.push(eq('tier', options.tier));
}
if (options.active !== undefined) {
conditions.push(eq('active', options.active));
}
return conditions.length > 0 ? expr(and(...conditions)) : null;
}
// Usage
const filter = buildUserFilter({ minAge: 18, tier: 'premium' });
if (filter) {
const users = await db.select(new Table('users')).where(filter);
}Search with multiple criteria
function searchProducts(criteria: {
minPrice?: Decimal;
maxPrice?: Decimal;
categories?: string[];
inStock?: boolean;
}) {
const conditions: ExprLike[] = [];
if (criteria.minPrice) {
conditions.push(gte('price', criteria.minPrice));
}
if (criteria.maxPrice) {
conditions.push(lte('price', criteria.maxPrice));
}
if (criteria.categories?.length) {
conditions.push(containsAny('categories', criteria.categories));
}
if (criteria.inStock !== undefined) {
conditions.push(eq('in_stock', criteria.inStock));
}
return expr(and(...conditions));
}See also
Query builders - Using expressions in queries
surql - Template tag for queries
BoundQuery - Parameterised queries
SelectPromise.where() - Using expressions with WHERE