Using Filters
The Boltic Tables SDK provides a powerful and flexible filtering system that supports multiple syntaxes and all field types. This guide covers every filtering capability available in the SDK.
Filter Syntax Options
The SDK supports three different filter syntaxes to accommodate different use cases:
1. Simple Where Clause (Recommended for most cases)
const { data: records } = await client.records.findAll("users", {
filters: [
{ field: "status", operator: "=", values: ["active"] },
{ field: "age", operator: ">=", values: [18] },
{
field: "department",
operator: "IN",
values: ["engineering", "product"],
},
],
});
2. Direct ApiFilter Format (For complex conditions)
const { data: records } = await client.records.findAll("users", {
filters: [
{ field: "status", operator: "=", values: ["active"] },
{ field: "age", operator: ">=", values: [18] },
{
field: "department",
operator: "IN",
values: ["engineering", "product"],
},
],
});
3. FilterBuilder (For programmatic construction)
import { createFilter } from "@boltic/sdk";
const filters = createFilter()
.equals("status", "active")
.greaterThanOrEqual("age", 18)
.in("department", ["engineering", "product"])
.build();
const { data: records } = await client.records.findAll("users", { filters });
Using Filters with Different Approaches
Direct Approach Examples
// Simple where clause
const { data: records } = await client.records.findAll("users", {
filters: [
{ field: "status", operator: "=", values: ["active"] },
{ field: "age", operator: ">=", values: [18] },
],
});
// ApiFilter format
const { data: records } = await client.records.findAll("users", {
filters: [{ field: "status", operator: "=", values: ["active"] }],
});
// FilterBuilder
const filters = createFilter().equals("status", "active").build();
const { data: records } = await client.records.findAll("users", { filters });
Supported Operators by Field Type
Based on the Boltic Tables filtering rules, here are all supported operators for each field type. The tables below show only the essential information needed for implementation:
Table Columns:
- Operator: The filter operator to use
- ApiFilter: The actual operator value for the API
- Values Format: How to format the values array
- Description: Brief explanation of what the operator does
Text Fields
Operator | ApiFilter | Values Format | Description |
---|---|---|---|
Is exactly | = | ['value'] | Exact match |
Not equals | != | ['value'] | Not equal to |
Contains (case-sensitive) | LIKE | ['%value%'] | Contains substring |
Contains (case-insensitive) | ILIKE | ['%value%'] | Case-insensitive contains |
Is one of | IN | ['value1', 'value2'] | Value in array |
Is empty | IS EMPTY | [true] or [false] | Field is empty |
Starts with | STARTS WITH | ['prefix'] | Begins with string |
Array-Specific Operators (for Dropdown/Multi-select fields)
Operator | ApiFilter | Values Format | Description |
---|---|---|---|
Array contains | @> | [['value']] | Array contains value |
Array not contains | NOT @> | [['value']] | Array doesn't contain |
Any element matches | ANY | ['value'] | Any array element equals |
Array is one of | IS ONE OF | ['value1', 'value2'] | Array value in list |
Text Field Examples
// Exact match
const { data: users } = await client.records.findAll("users", {
filters: [{ field: "name", operator: "=", values: ["John Doe"] }],
});
// Case-insensitive search
const { data: users } = await client.records.findAll("users", {
filters: [
{ field: "email", operator: "ILIKE", values: ["%@gmail.com"] },
{ field: "name", operator: "ILIKE", values: ["%john%"] },
],
});
// Multiple text conditions
const { data: users } = await client.records.findAll("users", {
filters: [
{ field: "role", operator: "!=", values: ["admin"] },
{ field: "department", operator: "IN", values: ["sales", "marketing"] },
{ field: "bio", operator: "IS EMPTY", values: [false] },
],
});
// Using ApiFilter format
const { data: users } = await client.records.findAll("users", {
filters: [
{ field: "name", operator: "STARTS WITH", values: ["Dr."] },
{ field: "email", operator: "LIKE", values: ["%@company.com"] },
],
});
Email Fields
Email fields support the same operators as text fields:
// Find specific email domains
const { data: users } = await client.records.findAll("users", {
filters: [{ field: "email", operator: "ILIKE", values: ["%@company.com"] }],
});
// Multiple email conditions
const { data: users } = await client.records.findAll("users", {
filters: [
{ field: "email", operator: "!=", values: ["[email protected]"] },
{ field: "backup_email", operator: "IS EMPTY", values: [true] },
],
});
Long Text Fields
Operator | SDK Syntax | ApiFilter | Description |
---|---|---|---|
Is exactly | value | = | Exact match |
Not equals | { $ne: value } | != | Not equal to |
Is empty | { $isEmpty: true } | IS EMPTY | Field is empty |
// Long text filtering
const { data: posts } = await client.records.findAll("blog_posts", {
filters: [
{ field: "content", operator: "!=", values: [""] },
{ field: "summary", operator: "IS EMPTY", values: [false] },
],
});
Number Fields
Operator | ApiFilter | Values Format | Description |
---|---|---|---|
Is exactly | = | [value] | Exact match |
Not equals | != | [value] | Not equal to |
Greater than | > | [value] | Greater than |
Greater than or equal | >= | [value] | Greater than or equal |
Less than | < | [value] | Less than |
Less than or equal | <= | [value] | Less than or equal |
Between | BETWEEN | [min, max] | Within range |
Is one of | IN | [value1, value2, value3] | Value in array |
Is empty | IS EMPTY | [true] or [false] | Field is empty |
Number Field Examples
// Basic number comparisons
const { data: users } = await client.records.findAll("users", {
filters: [
{ field: "age", operator: ">=", values: [18] }, // Adults only
{ field: "experience_years", operator: "BETWEEN", values: [2, 10] }, // Mid-level experience
],
});
// Score ranges
const { data: candidates } = await client.records.findAll("candidates", {
filters: [
{ field: "interview_score", operator: ">", values: [75] },
{ field: "technical_score", operator: ">=", values: [80] },
{ field: "years_experience", operator: "IN", values: [3, 5, 7, 10] },
],
});
// Using FilterBuilder
const experienceFilter = createFilter()
.greaterThanOrEqual("age", 25)
.lessThan("age", 50)
.between("salary_expectation", 50000, 120000)
.build();
Currency Fields
Currency fields support the same operators as number fields:
// Salary filtering
const { data: employees } = await client.records.findAll("employees", {
filters: [
{ field: "salary", operator: "BETWEEN", values: [50000, 100000] },
{ field: "bonus", operator: ">", values: [5000] },
],
});
// Product pricing
const { data: products } = await client.records.findAll("products", {
filters: [
{ field: "price", operator: "<=", values: [999.99] },
{ field: "cost", operator: ">", values: [0] },
],
});
Date-Time Fields
Operator | ApiFilter | Values Format | Description |
---|---|---|---|
Is exactly | = | ['2024-01-15T10:30:00Z'] | Exact match |
Not equals | != | [null] | Not equal to |
Is on or after | >= | ['2024-01-01T00:00:00Z'] | On or after date |
Is after | > | ['2024-01-01T00:00:00Z'] | After date |
Is before | < | ['2024-12-31T23:59:59Z'] | Before date |
Is on or before | <= | ['2024-06-30T23:59:59Z'] | On or before date |
Is between | BETWEEN | ['start_date', 'end_date'] | Within date range |
Is within | WITHIN | ['period_name'] | Predefined periods |
Date Within Periods
The WITHIN
operator supports predefined time periods:
Label | Value | Description |
---|---|---|
Yesterday | yesterday | Yesterday's date |
Today | today | Today's date |
Tomorrow | tomorrow | Tomorrow's date |
The past week | the-past-week | Last 7 days |
The next week | the-next-week | Next 7 days |
The past month | the-past-month | Last 30 days |
The next month | the-next-month | Next 30 days |
The past year | the-past-year | Last 365 days |
The next year | the-next-year | Next 365 days |
This calendar week | this-calendar-week | Current week (Mon-Sun) |
This calendar month | this-calendar-month | Current month |
This calendar year | this-calendar-year | Current year |
Year to date | year-to-date | From Jan 1 to now |
Month to date | month-to-date | From month start to now |
Week to date | week-to-date | From week start to now |
Last 7 days | last-7-days | Past 7 days |
Next 7 days | next-7-days | Next 7 days |
Last 30 days | last-30-days | Past 30 days |
Next 30 days | next-30-days | Next 30 days |
Last 365 days | last-365-days | Past 365 days |
Next 365 days | next-365-days | Next 365 days |
Date-Time Examples
// Basic date filtering
const { data: posts } = await client.records.findAll("blog_posts", {
filters: {
published_at: { $gte: "2024-01-01T00:00:00Z" },
created_at: { $lte: new Date().toISOString() },
},
});
// Date ranges
const { data: orders } = await client.records.findAll("orders", {
filters: {
order_date: {
$between: ["2024-01-01T00:00:00Z", "2024-03-31T23:59:59Z"],
},
},
});
// Using WITHIN operator with predefined periods
const { data: recentActivity } = await client.records.findAll("user_activity", {
filters: [{ field: "last_active", operator: "WITHIN", values: ["last-7-days"] }],
});
// Multiple date conditions
const { data: events } = await client.records.findAll("events", {
filters: {
start_date: { $gte: new Date().toISOString() },
end_date: { $lte: "2024-12-31T23:59:59Z" },
created_at: { $within: "this-calendar-month" },
},
});
Checkbox (Boolean) Fields
Operator | ApiFilter | Values Format | Description |
---|---|---|---|
True | = | [true] | Field is true |
False | != | [false] | Field is false |
// Boolean filtering
const { data: users } = await client.records.findAll("users", {
filters: {
is_active: true,
is_verified: true,
is_admin: false,
},
});
// Using ApiFilter format
const { data: users } = await client.records.findAll("users", {
filters: [
{ field: "is_active", operator: "=", values: [true] },
{ field: "is_deleted", operator: "=", values: [false] },
],
});
Dropdown Fields
Dropdown fields are stored as arrays and require special array operators for proper filtering:
// Single selection dropdown (stored as ['value'])
const { data: users } = await client.records.findAll("users", {
filters: [
{ field: "status", operator: "@>", values: [["active"]] }, // Check if array contains 'active'
{ field: "priority", operator: "ANY", values: ["high"] }, // Check if any element equals 'high'
],
});
// Multiple selection dropdown filtering
const { data: users } = await client.records.findAll("users", {
filters: [
{ field: "skills", operator: "@>", values: [["JavaScript"]] }, // Has JavaScript skill
{
field: "departments",
operator: "IS ONE OF",
values: ["engineering", "product"],
}, // Department is one of these
],
});
// Using ApiFilter format for arrays
const { data: users } = await client.records.findAll("users", {
filters: [
{ field: "skills", operator: "@>", values: [["JavaScript"]] }, // Array contains
{ field: "priority", operator: "ANY", values: ["high"] }, // Any element matches
{ field: "tags", operator: "IS ONE OF", values: ["urgent", "review"] }, // Is one of values
],
});
// FilterBuilder with array methods (currently supports arrayContains)
const filters = createFilter()
.arrayContains("skills", "TypeScript")
.arrayContains("departments", "engineering")
.build();
const { data: users } = await client.records.findAll("users", { filters });
Important Notes for Dropdown Fields:
- ✅ Use
$arrayContains
or@>
for checking if array contains a value - ✅ Use
$any
orANY
for checking if any array element matches - ❌ Don't use regular
$in
with nested arrays like[['value']]
- ❌ Don't use string operations like
$like
on array fields
Common Mistakes to Avoid:
// ❌ WRONG - This will cause "Malformed array literal" errors
const badFilters = [{ field: "dropdown_field", operator: "IN", values: ["Option 1", "Option 2"] }];
// ✅ CORRECT - Use array-specific operators
const goodFilters = [
{ field: "dropdown_field", operator: "@>", values: [["Option 1"]] }, // Contains
{ field: "dropdown_field", operator: "ANY", values: ["Option 2"] }, // Any element matches
];
Phone Number Fields
Phone fields support the same operators as text fields:
// Phone number filtering
const { data: users } = await client.records.findAll("users", {
filters: [
{ field: "phone", operator: "STARTS WITH", values: ["+1"] },
{ field: "mobile", operator: "IS EMPTY", values: [false] },
],
});
Link Fields
Link fields support the same operators as text fields:
// URL filtering
const { data: websites } = await client.records.findAll("websites", {
filters: [
{ field: "url", operator: "LIKE", values: ["https://%"] },
{ field: "backup_url", operator: "IS EMPTY", values: [true] },
],
});
JSON Fields
Operator | SDK Syntax | ApiFilter | Description |
---|---|---|---|
Is exactly | value | = | Exact match |
Not equals | { $ne: value } | != | Not equal to |
Is empty | { $isEmpty: true } | IS EMPTY | Field is empty |
// JSON field filtering
const { data: users } = await client.records.findAll("users", {
filters: [
{ field: "preferences", operator: "!=", values: [null] },
{ field: "metadata", operator: "IS EMPTY", values: [false] },
],
});
// Exact JSON matching
const { data: configs } = await client.records.findAll("configurations", {
filters: [
{
field: "settings",
operator: "=",
values: [{ theme: "dark", language: "en" }],
},
],
});
Vector Fields (vector, halfvec, sparsevec)
Vector fields support both standard comparison operators and specialized distance operators.
⚠️ Critical Note for Vector Operators:
When using vector distance operators (<+>
, <->
, *
, <#>
, <=>
), the vector values must be passed as strings wrapped in double quotes, not as raw arrays. The backend expects the vector in string format like "[0.1,0.2,0.3]"
.
Correct Format:
// ✅ CORRECT - Vector as string in double quotes
{ field: 'embedding', operator: '<->', values: ['[0.1,0.2,0.3]'] }
// ❌ WRONG - Raw array
{ field: 'embedding', operator: '<->', values: [[0.1,0.2,0.3]] }
Standard Operators
Operator | ApiFilter | Values Format | Description |
---|---|---|---|
Is exactly | = | [value] | Exact match |
Not equals | != | [value] | Not equal to |
Greater than | > | [threshold] | Greater than |
Greater than or equal | >= | [threshold] | Greater than or equal |
Less than | < | [threshold] | Less than |
Less than or equal | <= | [threshold] | Less than or equal |
Between | BETWEEN | [min, max] | Within range |
Is one of | IN | [vector1, vector2] | Vector in array |
Is empty | IS EMPTY | [true] or [false] | Field is empty |
Distance Operators
Operator | ApiFilter | Values Format | Description |
---|---|---|---|
Manhattan Distance (L1) | <+> | ["[0.1,0.2,0.3]"] | Sum of absolute differences |
Euclidean Distance (L2) | <-> | ["[0.1,0.2,0.3]"] | Straight-line distance |
Inner Product | * | ["[0.1,0.2,0.3]"] | Dot product |
Negative Inner Product | <#> | ["[0.1,0.2,0.3]"] | Negative dot product |
Cosine Distance | <=> | ["[0.1,0.2,0.3]"] | Angular distance |
⚠️ Important: Vector values must be strings wrapped in double quotes, not raw arrays. Use ["[0.1,0.2,0.3]"]
format.
Vector Examples
// Similarity search using Euclidean distance
// Note: Vector must be a string wrapped in double quotes
const queryVector = "[0.1, 0.2, 0.3, 0.4, 0.5]";
const { data: similar } = await client.records.findAll("documents", {
filters: [
{
field: "content_vector",
operator: "<->", // Euclidean distance
values: [queryVector], // Vector as string in array
},
],
sort: [{ field: "content_vector", direction: "asc" }], // Closest first
page: { page_no: 1, page_size: 10 },
});
// Different distance operators
const manhattanResults = await client.records.findAll("embeddings", {
filters: [
{ field: "vector", operator: "<+>", values: [queryVector] }, // Manhattan
],
});
const cosineResults = await client.records.findAll("embeddings", {
filters: [
{ field: "vector", operator: "<=>", values: [queryVector] }, // Cosine
],
});
// Standard vector operations
const { data: vectors } = await client.records.findAll("embeddings", {
filters: [
{ field: "vector", operator: "!=", values: [null] },
{ field: "confidence_score", operator: ">=", values: [0.8] },
],
});
// Sparse vector filtering
const { data: sparse } = await client.records.findAll("sparse_embeddings", {
filters: [{ field: "sparse_vector", operator: "=", values: ["{1:1,3:2,5:3}/5"] }],
});
⚠️ Important Note for Vector Operators:
When using vector distance operators (<+>
, <->
, *
, <#>
, <=>
), the vector values must be passed as strings wrapped in quotes, not as raw arrays. The backend expects the vector in string format like "[0.1,0.2,0.3]"
.
Complex Filter Combinations
Multiple Field Filtering (AND Logic)
All conditions in the same filter object are combined with AND logic:
const { data: users } = await client.records.findAll("users", {
filters: {
status: "active", // AND
age: { $gte: 25 }, // AND
department: "engineering", // AND
is_verified: true, // AND
},
});
Using FilterBuilder for Complex Conditions
import { createFilter } from "@boltic/sdk";
// Complex multi-field filter
const complexFilter = createFilter()
.equals("status", "active")
.greaterThanOrEqual("age", 25)
.lessThan("age", 65)
.in("department", ["engineering", "product", "design"])
.like("email", "%@company.com")
.between("join_date", "2020-01-01T00:00:00Z", "2024-12-31T23:59:59Z")
.equals("is_verified", true)
.isNull("termination_date")
.build();
const { data: employees } = await client.records.findAll("employees", {
filters: complexFilter,
sort: [{ field: "join_date", direction: "desc" }],
page: { page_no: 1, page_size: 50 },
});
Dynamic Filter Building
function buildUserFilter(criteria: {
status?: string;
ageRange?: { min: number; max: number };
departments?: string[];
verified?: boolean;
searchTerm?: string;
}) {
const filterBuilder = createFilter();
if (criteria.status) {
filterBuilder.equals("status", criteria.status);
}
if (criteria.ageRange) {
filterBuilder.between("age", criteria.ageRange.min, criteria.ageRange.max);
}
if (criteria.departments && criteria.departments.length > 0) {
filterBuilder.in("department", criteria.departments);
}
if (criteria.verified !== undefined) {
filterBuilder.equals("is_verified", criteria.verified);
}
if (criteria.searchTerm) {
filterBuilder.like("name", `%${criteria.searchTerm}%`);
}
return filterBuilder.build();
}
// Usage
const filters = buildUserFilter({
status: "active",
ageRange: { min: 25, max: 45 },
departments: ["engineering", "product"],
verified: true,
searchTerm: "john",
});
const { data: users } = await client.records.findAll("users", { filters });
Advanced Filtering Patterns
Null and Empty Value Handling
// Find records with missing data
const { data: incomplete } = await client.records.findAll("users", {
filters: [
{ field: "phone", operator: "IS EMPTY", values: [] },
{ field: "address", operator: "IS NULL", values: [] },
],
});
// Find complete profiles
const completeProfileFilter = createFilter()
.notEquals("name", "")
.notEquals("email", "")
.isNotNull("phone")
.isNotNull("address")
.build();
Date Range Queries
// This quarter's data
const thisQuarter = await client.records.findAll("sales", {
filters: [{ field: "sale_date", operator: "WITHIN", values: ["this-calendar-month"] }],
});
// Custom date range
const customRange = await client.records.findAll("orders", {
filters: [
{
field: "order_date",
operator: "BETWEEN",
values: ["2024-01-01T00:00:00Z", "2024-03-31T23:59:59Z"],
},
],
});
// Recent activity
const recentActivity = await client.records.findAll("user_sessions", {
filters: [{ field: "last_active", operator: "WITHIN", values: ["last-7-days"] }],
});
Filter Usage Patterns Summary
Correct Values Format for Different Operators
Based on the backend implementation, here's how to correctly format values for each operator:
1. Regular Field Operators (text, number, date, boolean)
- Values Format:
['value']
or['value1', 'value2']
(flat arrays) - Examples:
{ field: 'name', operator: '=', values: ['John'] }
{ field: 'age', operator: 'IN', values: [25, 30, 35] }
{ field: 'status', operator: '!=', values: ['inactive'] }
2. Array Field Operators (dropdown, multi-select)
- @> (Array Contains):
values: [['value']]
(nested array)- Example:
{ field: 'skills', operator: '@>', values: [['JavaScript']] }
- Example:
- ANY (Any Element):
values: ['value']
(flat array)- Example:
{ field: 'tags', operator: 'ANY', values: ['urgent'] }
- Example:
- IS ONE OF (Array Overlap):
values: ['value1', 'value2']
(flat array)- Example:
{ field: 'departments', operator: 'IS ONE OF', values: ['eng', 'sales'] }
- Example:
3. Vector Field Operators
- Distance Operators:
values: ["[0.1,0.2,0.3]"]
(string representation of vector in array)- Example:
{ field: 'embedding', operator: '<->', values: ["[0.1,0.2,0.3]"] }
- ⚠️ Important: Vector values must be strings wrapped in double quotes, not raw arrays
- Example:
- Standard Operators:
values: [value]
(flat array)- Example:
{ field: 'confidence', operator: '>', values: [0.8] }
- Example:
Key Points to Remember:
- IN operator: Always use flat arrays
['value1', 'value2']
- Array operators: Use nested arrays
[['value']]
for containment, flat arrays for overlap - All values: Must be wrapped in an array, even for single values
- Null values: Use
[null]
notnull
- Vector operators: Vector values must be strings wrapped in double quotes like
["[0.1,0.2,0.3]"]
, not raw arrays
Best Practices
1. Use Indexed Fields for Primary Filters
// Good - use indexed fields first
const { data: results } = await client.records.findAll("users", {
filters: [
{ field: "status", operator: "=", values: ["active"] }, // Indexed field
{ field: "department_id", operator: "=", values: [123] }, // Indexed field
{ field: "name", operator: "ILIKE", values: ["%john%"] }, // Secondary filter
],
});
2. Combine Filters Efficiently
// Good - specific filters first
const efficientFilter = createFilter()
.equals("status", "active") // Most selective first
.in("department", ["eng", "product"]) // Moderately selective
.greaterThan("age", 25) // Less selective
.like("bio", "%developer%") // Least selective last
.build();
3. Use Appropriate Page Sizes
// Good - reasonable page size with filters
const { data: records } = await client.records.findAll("large_table", {
filters: [{ field: "status", operator: "=", values: ["active"] }],
page: { page_no: 1, page_size: 100 }, // Manageable size
});