Skip to content

Query and filter records

Write a HOQS query that returns exactly the records and fields you need, filtered, sorted, and paginated.

Every HOQS query is sent as a POST to the query endpoint, with the query string in the query field:

{
"query": "SELECT * FROM core.Contact LIMIT 10"
}

SELECT * FROM core.Contact LIMIT 10 returns up to 10 Contact records with all fields, including system fields (id, createdAt, updatedAt), inherited fields, and user-defined fields. LIMIT is required on every top-level query.

The response includes the matching records, total count, and pagination info:

{
"data": [
{
"id": "019502a4-b4e7-7f3a-8c1e-0a1b2c3d4e5f",
"objectType": "core.Contact",
"createdAt": "2024-03-15T10:30:00.000Z",
"updatedAt": "2024-03-15T10:30:00.000Z",
"firstName": "Alice",
"lastName": "Chen",
"email": "alice@acme.com",
"status": "active"
}
],
"totalSize": 42,
"hasMore": true
}

Simple equality:

SELECT * FROM core.Contact WHERE status = 'active' LIMIT 50

Combine conditions with AND and OR. Without parentheses, AND binds tighter than OR:

SELECT * FROM core.Contact
WHERE (status = 'open' OR status = 'pending') AND priority = 'high'
LIMIT 50

3. Check for null with IS NULL / IS NOT NULL

Section titled “3. Check for null with IS NULL / IS NOT NULL”
SELECT * FROM core.Contact WHERE email IS NOT NULL LIMIT 50

Do not write email = NULL — HOQS’s validator rejects it and tells you to use IS NULL instead (see Querying Data with HOQS for why).

4. Match a list of values with IN / NOT IN

Section titled “4. Match a list of values with IN / NOT IN”
SELECT * FROM core.Contact
WHERE status IN ('active', 'pending', 'review')
LIMIT 100
SELECT * FROM core.Contact
WHERE status NOT IN ('closed', 'archived')
LIMIT 100

LIKE is case-sensitive, ILIKE is case-insensitive. Use % for any sequence of characters:

SELECT * FROM core.Contact WHERE lastName LIKE 'Ch%' LIMIT 50
SELECT * FROM core.Contact WHERE email ILIKE '%@acme%' LIMIT 50

Inclusive on both bounds:

SELECT * FROM core.Contact WHERE age BETWEEN 25 AND 45 LIMIT 50
SELECT * FROM core.Opportunity WHERE closedDate WITHIN THIS QUARTER LIMIT 100
SELECT * FROM core.Opportunity WHERE closedDate WITHIN LAST 30 DAYS LIMIT 100
SELECT * FROM core.Contact WHERE createdAt WITHIN TODAY LIMIT 50

Available ranges: TODAY, YESTERDAY, TOMORROW, THIS/LAST/NEXT WEEK/MONTH/QUARTER/YEAR, and LAST/NEXT N DAYS/WEEKS/MONTHS/QUARTERS/YEARS.

For boolean fields, the field name alone is shorthand for = true, and NOT field for = false:

SELECT * FROM core.Contact WHERE isActive LIMIT 50
SELECT * FROM core.Contact WHERE NOT isArchived LIMIT 50
SELECT firstName, lastName, email FROM core.Contact LIMIT 50

The response always includes the system fields (id, createdAt, updatedAt) in addition to the fields you list.

SELECT * FROM core.Contact ORDER BY lastName ASC LIMIT 50
SELECT * FROM core.Opportunity ORDER BY createdAt DESC LIMIT 25

Default sort direction is ASC. Use OFFSET for pagination:

SELECT * FROM core.Contact ORDER BY lastName ASC LIMIT 50 OFFSET 100
SELECT firstName, account.name, account.industry
FROM core.Contact
WHERE account.industry = 'Technology'
ORDER BY account.name ASC
LIMIT 50

The first segment (account) must be a lookup field on the object being queried. The second segment (name, industry) is a field on the lookup’s target. Only single-hop traversal is supported — account.name works, account.owner.name does not.

Run your query and check totalSize and hasMore in the response against what you expect. If a field name is misspelled, the error response includes the position and a suggestion, e.g. Field 'statux' does not exist on object 'core.Contact'. (at 1:42) Did you mean 'status'?.