ObjectQL API Reference
Complete API reference for ObjectQL query language
ObjectQL API Reference
ObjectQL is HotCRM's type-safe query language for data operations. This reference covers all available methods and syntax.
Database Client
Import the database client:
import { db } from '@hotcrm/core';Query Operations
db.find()
Find records using array-based filter syntax.
Signature:
async find<T>(
objectName: string,
options: ObjectQLFindOptions
): Promise<T[]>Parameters:
objectName: Object name (e.g.,'Account','Opportunity')options.filters: Array of filter conditionsoptions.fields: Array of field names to returnoptions.sort: Sort orderoptions.limit: Maximum number of recordsoptions.skip: Number of records to skip (pagination)
Examples:
// Find all accounts
const accounts = await db.find('Account', {});
// Find with filters
const techAccounts = await db.find('Account', {
filters: [['Industry', '=', 'Technology']]
});
// Multiple filters (AND)
const results = await db.find('Opportunity', {
filters: [
['Stage', '!=', 'Closed Lost'],
['Amount', '>', 10000],
['CloseDate', '>=', '2026-01-01']
]
});
// Select specific fields
const accounts = await db.find('Account', {
fields: ['Name', 'Industry', 'Phone'],
filters: [['Status', '=', 'Active']]
});
// Sort results
const opportunities = await db.find('Opportunity', {
filters: [],
sort: [['Amount', 'desc']],
limit: 10
});
// Pagination
const page2 = await db.find('Lead', {
filters: [['Status', '=', 'New']],
skip: 20,
limit: 20
});db.query()
Query records using object-based filter syntax with relationship support.
Signature:
async query<T>(
query: ObjectQLQuery
): Promise<ObjectQLResult<T>>Parameters:
query.object: Object namequery.fields: Array of field namesquery.filters: Object-based filter conditionsquery.related: Related object queriesquery.orderBy: Sort configurationquery.limit: Maximum recordsquery.offset: Skip records
Examples:
// Basic query
const result = await db.query({
object: 'Account',
fields: ['Name', 'Industry'],
filters: {
Industry: { $in: ['Technology', 'Finance'] },
AnnualRevenue: { $gt: 1000000 }
},
limit: 50
});
console.log(result.records);
console.log(result.totalCount);
console.log(result.hasMore);
// Query with relationships
const account = await db.query({
object: 'Account',
fields: ['Name', 'Industry'],
filters: { Id: 'acc_123' },
related: {
Opportunities: {
fields: ['Name', 'Amount', 'Stage'],
filters: {
Stage: { $ne: 'Closed Lost' }
}
},
Contacts: {
fields: ['FirstName', 'LastName', 'Email']
}
}
});
// Nested relationships
const opportunity = await db.query({
object: 'Opportunity',
fields: ['Name', 'Amount'],
filters: { Id: 'opp_123' },
related: {
Account: {
fields: ['Name'],
related: {
Contacts: {
fields: ['FirstName', 'LastName']
}
}
}
}
});Document Operations
db.doc.create()
Create a new record.
Signature:
async create(
objectName: string,
data: Record<string, any>
): Promise<any>Examples:
// Create account
const account = await db.doc.create('Account', {
Name: 'Acme Corporation',
Industry: 'Technology',
AnnualRevenue: 5000000,
Phone: '+1-555-0123',
Website: 'https://acme.com'
});
console.log('Created account:', account.Id);
// Create with lookup relationship
const contact = await db.doc.create('Contact', {
FirstName: 'John',
LastName: 'Doe',
Email: 'john.doe@acme.com',
AccountId: account.Id // Lookup relationship
});
// Create with default values
const opportunity = await db.doc.create('Opportunity', {
Name: 'Enterprise Deal',
AccountId: account.Id,
Stage: 'Prospecting', // Default if not provided
CloseDate: '2026-12-31',
Amount: 100000
});db.doc.update()
Update an existing record.
Signature:
async update(
objectName: string,
id: string,
data: Record<string, any>
): Promise<void>Examples:
// Update single field
await db.doc.update('Account', accountId, {
Phone: '+1-555-9999'
});
// Update multiple fields
await db.doc.update('Opportunity', oppId, {
Stage: 'Negotiation',
Amount: 120000,
NextStep: 'Schedule contract review'
});
// Update with null (clear field)
await db.doc.update('Lead', leadId, {
ConvertedDate: null
});db.doc.delete()
Delete a record.
Signature:
async delete(
objectName: string,
id: string
): Promise<void>Examples:
// Delete record
await db.doc.delete('Lead', leadId);
// Delete with error handling
try {
await db.doc.delete('Account', accountId);
} catch (error) {
if (error.code === 'REFERENCE_ERROR') {
console.error('Cannot delete: Account has related records');
}
}db.doc.get()
Get a single record by ID.
Signature:
async get(
objectName: string,
id: string,
options?: { fields?: string[] }
): Promise<any>Examples:
// Get all fields
const account = await db.doc.get('Account', accountId);
// Get specific fields only
const account = await db.doc.get('Account', accountId, {
fields: ['Name', 'Industry', 'Phone']
});
// Get with error handling
const account = await db.doc.get('Account', accountId);
if (!account) {
console.error('Account not found');
}Batch Operations
db.batch.create()
Create multiple records in a single transaction.
Signature:
async create(
objectName: string,
records: Record<string, any>[]
): Promise<any[]>Examples:
// Create multiple contacts
const contacts = await db.batch.create('Contact', [
{
FirstName: 'John',
LastName: 'Doe',
Email: 'john@example.com'
},
{
FirstName: 'Jane',
LastName: 'Smith',
Email: 'jane@example.com'
},
{
FirstName: 'Bob',
LastName: 'Johnson',
Email: 'bob@example.com'
}
]);
console.log(`Created ${contacts.length} contacts`);
contacts.forEach(c => console.log(c.Id));db.batch.update()
Update multiple records in a single transaction.
Signature:
async update(
objectName: string,
updates: Array<{ id: string; data: Record<string, any> }>
): Promise<void>Examples:
// Update multiple leads
await db.batch.update('Lead', [
{ id: 'lead_1', data: { Status: 'Working' } },
{ id: 'lead_2', data: { Status: 'Converted' } },
{ id: 'lead_3', data: { Status: 'Nurturing' } }
]);
// Update with different fields
await db.batch.update('Opportunity', [
{ id: 'opp_1', data: { Stage: 'Closed Won', CloseDate: '2026-01-15' } },
{ id: 'opp_2', data: { Amount: 50000 } },
{ id: 'opp_3', data: { NextStep: 'Schedule demo' } }
]);db.batch.delete()
Delete multiple records in a single transaction.
Signature:
async delete(
objectName: string,
ids: string[]
): Promise<void>Examples:
// Delete multiple records
await db.batch.delete('Lead', [
'lead_1',
'lead_2',
'lead_3'
]);
// Delete with confirmation
const idsToDelete = ['acc_1', 'acc_2'];
if (confirm(`Delete ${idsToDelete.length} accounts?`)) {
await db.batch.delete('Account', idsToDelete);
}Aggregation Operations
db.count()
Count records matching filters.
Signature:
async count(
objectName: string,
options: { filters?: ObjectQLArrayFilter }
): Promise<number>Examples:
// Count all records
const total = await db.count('Account', {});
// Count with filter
const techCount = await db.count('Account', {
filters: [['Industry', '=', 'Technology']]
});
// Count multiple conditions
const activeCount = await db.count('Opportunity', {
filters: [
['IsClosed', '=', false],
['Amount', '>', 10000]
]
});
console.log(`${activeCount} active opportunities over $10K`);db.sum()
Calculate sum of a numeric field.
Signature:
async sum(
objectName: string,
field: string,
options: { filters?: ObjectQLArrayFilter }
): Promise<number>Examples:
// Total revenue
const totalRevenue = await db.sum('Opportunity', 'Amount', {
filters: [['Stage', '=', 'Closed Won']]
});
console.log(`Total revenue: $${totalRevenue.toLocaleString()}`);
// Pipeline value
const pipelineValue = await db.sum('Opportunity', 'Amount', {
filters: [['IsClosed', '=', false]]
});db.avg()
Calculate average of a numeric field.
Signature:
async avg(
objectName: string,
field: string,
options: { filters?: ObjectQLArrayFilter }
): Promise<number>Examples:
// Average deal size
const avgDealSize = await db.avg('Opportunity', 'Amount', {
filters: [['Stage', '=', 'Closed Won']]
});
console.log(`Average deal size: $${avgDealSize.toLocaleString()}`);
// Average by industry
const avgRevenue = await db.avg('Account', 'AnnualRevenue', {
filters: [['Industry', '=', 'Technology']]
});db.min() / db.max()
Get minimum or maximum value of a field.
Signature:
async min(
objectName: string,
field: string,
options: { filters?: ObjectQLArrayFilter }
): Promise<number>
async max(
objectName: string,
field: string,
options: { filters?: ObjectQLArrayFilter }
): Promise<number>Examples:
// Smallest and largest deals
const minDeal = await db.min('Opportunity', 'Amount', {
filters: [['Stage', '=', 'Closed Won']]
});
const maxDeal = await db.max('Opportunity', 'Amount', {
filters: [['Stage', '=', 'Closed Won']]
});
console.log(`Deal range: $${minDeal} - $${maxDeal}`);db.groupBy()
Group records and perform aggregations.
Signature:
async groupBy(
objectName: string,
options: {
groupBy: string;
aggregations: Record<string, { function: string; field: string }>;
filters?: ObjectQLArrayFilter;
}
): Promise<any[]>Examples:
// Count by stage
const stageCount = await db.groupBy('Opportunity', {
groupBy: 'Stage',
aggregations: {
count: { function: 'COUNT', field: 'Id' },
totalAmount: { function: 'SUM', field: 'Amount' },
avgAmount: { function: 'AVG', field: 'Amount' }
}
});
stageCount.forEach(group => {
console.log(`${group.Stage}: ${group.count} opps, $${group.totalAmount}`);
});
// Revenue by month
const monthlyRevenue = await db.groupBy('Opportunity', {
groupBy: 'CloseDate.month',
aggregations: {
revenue: { function: 'SUM', field: 'Amount' },
deals: { function: 'COUNT', field: 'Id' }
},
filters: [
['Stage', '=', 'Closed Won'],
['CloseDate', 'this_year']
]
});Transaction Operations
db.transaction()
Execute multiple operations atomically.
Signature:
async transaction(
callback: (trx: ObjectQLEngine) => Promise<void>
): Promise<void>Examples:
// Create related records atomically
await db.transaction(async (trx) => {
// Create account
const account = await trx.doc.create('Account', {
Name: 'New Customer Inc.',
Industry: 'Technology'
});
// Create contact
const contact = await trx.doc.create('Contact', {
FirstName: 'John',
LastName: 'Doe',
AccountId: account.Id,
Email: 'john@newcustomer.com'
});
// Create opportunity
await trx.doc.create('Opportunity', {
Name: 'Initial Deal',
AccountId: account.Id,
ContactId: contact.Id,
Amount: 50000,
Stage: 'Prospecting',
CloseDate: '2026-12-31'
});
// If any operation fails, all are rolled back
});
// Transfer ownership atomically
await db.transaction(async (trx) => {
const newOwnerId = 'user_456';
// Update account
await trx.doc.update('Account', accountId, {
OwnerId: newOwnerId
});
// Update all related opportunities
const opps = await trx.find('Opportunity', {
filters: [
['AccountId', '=', accountId],
['IsClosed', '=', false]
]
});
for (const opp of opps) {
await trx.doc.update('Opportunity', opp.Id, {
OwnerId: newOwnerId
});
}
});Filter Operators
Comparison Operators
// Equal
[['Status', '=', 'Active']]
// Not equal
[['Status', '!=', 'Closed']]
[['Status', '<>', 'Closed']] // Alternative syntax
// Greater than / Less than
[['Amount', '>', 10000]]
[['Amount', '>=', 10000]]
[['Amount', '<', 100000]]
[['Amount', '<=', 100000]]
// Between
[['Amount', 'between', [10000, 50000]]]List Operators
// In list
[['Industry', 'in', ['Technology', 'Finance', 'Healthcare']]]
// Not in list
[['Status', 'not in', ['Closed Won', 'Closed Lost']]]String Operators
// Like (contains)
[['Name', 'like', '%Corp%']]
// Starts with
[['Name', 'like', 'Acme%']]
// Ends with
[['Name', 'like', '%Inc']]
// Case-insensitive
[['Email', 'ilike', '%@ACME.COM']]Null Operators
// Is null
[['ParentAccountId', 'is null']]
[['Email', '=', null]]
// Is not null
[['Email', 'is not null']]
[['Email', '!=', null]]Date Operators
// Specific date
[['CloseDate', '=', '2026-01-15']]
// Date range
[['CloseDate', 'between', ['2026-01-01', '2026-12-31']]]
// Relative dates
[['CreatedDate', 'last_n_days', 7]]
[['CreatedDate', 'next_n_days', 30]]
// Date literals
[['CloseDate', 'today']]
[['CloseDate', 'yesterday']]
[['CloseDate', 'tomorrow']]
[['CloseDate', 'this_week']]
[['CloseDate', 'this_month']]
[['CloseDate', 'this_quarter']]
[['CloseDate', 'this_year']]
[['CloseDate', 'last_week']]
[['CloseDate', 'last_month']]
[['CloseDate', 'last_quarter']]
[['CloseDate', 'last_year']]Special Variables
// Current user
[['OwnerId', '=', '$currentUser']]
// Current date/time
[['CreatedDate', '=', '$now']]
[['CloseDate', '=', '$today']]Sort Options
// Single field ascending
sort: [['Name', 'asc']]
// Single field descending
sort: [['Amount', 'desc']]
// Multiple fields
sort: [
['Priority', 'desc'],
['DueDate', 'asc']
]
// Shorthand string syntax
sort: 'Name' // asc by default
sort: '-Amount' // desc with minus prefixType Definitions
ObjectQLFindOptions
interface ObjectQLFindOptions {
filters?: ObjectQLArrayFilter;
fields?: string[];
sort?: string | string[];
limit?: number;
skip?: number;
}ObjectQLArrayFilter
type ObjectQLArrayFilter = [string, string, any][];
// Examples
[['Status', '=', 'Active']]
[['Amount', '>', 10000], ['Stage', '!=', 'Lost']]ObjectQLQuery
interface ObjectQLQuery {
object: string;
fields?: string[];
filters?: ObjectQLFilter;
related?: {
[relationName: string]: {
fields?: string[];
filters?: ObjectQLFilter;
limit?: number;
};
};
orderBy?: string | { field: string; direction: 'asc' | 'desc' }[];
limit?: number;
offset?: number;
}ObjectQLResult
interface ObjectQLResult<T = any> {
records: T[];
totalCount: number;
hasMore: boolean;
}Error Handling
try {
const account = await db.doc.create('Account', {
Name: 'Test Account'
});
} catch (error) {
if (error.code === 'VALIDATION_ERROR') {
console.error('Validation failed:', error.details);
} else if (error.code === 'DUPLICATE_ERROR') {
console.error('Record already exists:', error.field);
} else if (error.code === 'PERMISSION_ERROR') {
console.error('Access denied:', error.message);
} else if (error.code === 'NOT_FOUND') {
console.error('Record not found');
} else {
console.error('Unexpected error:', error.message);
}
}Error Codes
VALIDATION_ERROR: Field validation failedDUPLICATE_ERROR: Unique constraint violationPERMISSION_ERROR: Insufficient permissionsNOT_FOUND: Record not foundREFERENCE_ERROR: Foreign key constraint violationREQUIRED_ERROR: Required field missing
Performance Tips
1. Select Only Needed Fields
// ✅ Good
const accounts = await db.find('Account', {
fields: ['Name', 'Industry']
});
// ❌ Bad
const accounts = await db.find('Account', {});2. Use Batch Operations
// ✅ Good
await db.batch.create('Contact', contacts);
// ❌ Bad
for (const contact of contacts) {
await db.doc.create('Contact', contact);
}3. Filter at Database Level
// ✅ Good
const active = await db.find('Account', {
filters: [['Status', '=', 'Active']]
});
// ❌ Bad
const all = await db.find('Account', {});
const active = all.filter(a => a.Status === 'Active');4. Use Pagination
// ✅ Good
const page = await db.find('Lead', {
limit: 50,
skip: pageNumber * 50
});
// ❌ Bad
const all = await db.find('Lead', {});5. Use Transactions for Related Operations
// ✅ Good
await db.transaction(async (trx) => {
await trx.doc.create('Account', {...});
await trx.doc.create('Contact', {...});
});
// ❌ Bad
await db.doc.create('Account', {...});
await db.doc.create('Contact', {...});Next Steps
- Creating Objects - Define objects to query
- Business Logic - Use ObjectQL in hooks
- REST API - HTTP endpoints