InstantDB uses InstaQL, a declarative query language that provides powerful filtering, sorting, and relationship traversal capabilities. All queries are reactive by default and automatically update when underlying data changes.
Query Methods
query()
Create a reactive query that automatically updates when data changes.
Signal<QueryResult> query(Map<String, dynamic> query, {bool syncedOnly = false})Parameters:
query(Map<String, dynamic>): InstaQL query objectsyncedOnly(bool, optional): If true, only returns entities that are synced to the cloud.
Returns: Signal<QueryResult> - Reactive signal containing query results
Example:
final todosSignal = db.query({
'todos': {
'where': {'completed': false},
'orderBy': {'createdAt': 'desc'},
'limit': 10,
},
});
// Use in reactive widgets
Watch((context) {
final result = todosSignal.value;
final todos = result.data?['todos'] ?? [];
return TodoList(todos: todos);
});queryOnce()
Execute a one-time query without creating a subscription.
Future<QueryResult> queryOnce(Map<String, dynamic> query)Parameters:
query(Map<String, dynamic>): InstaQL query object
Returns: Future<QueryResult> - Query result
Example:
final result = await db.queryOnce({
'users': {
'where': {'role': 'admin'},
'limit': 5,
},
});
final adminUsers = result.data?['users'] ?? [];
print('Found ${adminUsers.length} admin users');QueryResult
Result object returned by query operations.
class QueryResult {
final Map<String, dynamic>? data;
final String? error;
final bool isLoading;
}Properties:
data(Map<String, dynamic>?): Query result data indexed by entity typeerror(String?): Error message if query failedisLoading(bool): Whether query is currently loading
InstaQL Syntax
Basic Query Structure
{
'entityType': {
'where': {...}, // Filtering conditions
'orderBy': {...}, // Sorting specification
'limit': 10, // Maximum results
'offset': 20, // Skip results (pagination)
'include': {...}, // Include related entities
}
}Simple Queries
// Get all entities
{'users': {}}
// Get with limit
{'posts': {'limit': 10}}
// Get specific entity by ID
{'users': {'where': {'id': 'user-123'}}}
// Multiple entity types
{
'users': {'limit': 5},
'posts': {'where': {'published': true}},
}Where Conditions
Equality
// Exact match
{'users': {'where': {'name': 'John Doe'}}}
// Multiple conditions (AND)
{
'posts': {
'where': {
'published': true,
'authorId': 'user-123',
}
}
}Comparison Operators
{
'posts': {
'where': {
'createdAt': {
'\$gte': DateTime.now().subtract(Duration(days: 7)).millisecondsSinceEpoch,
'\$lt': DateTime.now().millisecondsSinceEpoch,
},
'viewCount': {'\$gt': 100},
'rating': {'\$lte': 5},
}
}
}Available operators:
$gt: Greater than$gte: Greater than or equal$lt: Less than$lte: Less than or equal$ne: Not equal$eq: Equal (explicit equality)
String Operators
{
'users': {
'where': {
'email': {'$like': '%@gmail.com'}, // Contains pattern (% matches any characters)
'name': {'$ilike': '%john%'}, // Case-insensitive like
}
}
}Array Operators
{
'posts': {
'where': {
'tags': {'$contains': 'flutter'}, // Array contains value
'skills': {'$size': 3}, // Array matches exact size
'permissions': {'$in': ['read', 'write']}, // Value in list
'blockedUsers': {'$nin': ['user-123']}, // Value not in list
}
}
}Existence Operators
{
'users': {
'where': {
'profilePicture': {'$exists': true}, // Field exists (is not null)
'deletedAt': {'$isNull': true}, // Field is null
'verifiedAt': {'$isNull': false}, // Field is not null (using $isNull: false)
}
}
}Logical Operators
AND Operator
// Implicit AND (default)
{
'users': {
'where': {
'active': true,
'role': 'user', // Both conditions must be true
}
}
}
// Explicit AND
{
'users': {
'where': {
'\$and': [
{'age': {'\$gte': 18}},
{'status': 'verified'},
]
}
}
}OR Operator
{
'users': {
'where': {
'\$or': [
{'role': 'admin'},
{'role': 'moderator'},
{'permissions': {'\$contains': 'admin'}},
]
}
}
}NOT Operator
{
'posts': {
'where': {
'\$not': {
'status': 'deleted',
}
}
}
}Complex Logic
{
'users': {
'where': {
'\$and': [
{'active': true},
{
'\$or': [
{'role': 'admin'},
{
'\$and': [
{'role': 'user'},
{'permissions': {'\$contains': 'write'}},
]
}
]
}
]
}
}
}Sorting (orderBy)
Single Field
// Ascending order
{'posts': {'orderBy': {'createdAt': 'asc'}}}
// Descending order
{'posts': {'orderBy': {'createdAt': 'desc'}}}Multiple Fields
{
'posts': {
'orderBy': [
{'priority': 'desc'}, // Primary sort
{'createdAt': 'asc'}, // Secondary sort
{'title': 'asc'}, // Tertiary sort
]
}
}Dynamic Sorting
// Sort by different fields based on conditions
String sortField = userPreference == 'date' ? 'createdAt' : 'title';
String sortDirection = ascending ? 'asc' : 'desc';
final query = {
'posts': {
'orderBy': {sortField: sortDirection},
}
};Pagination
Limit and Offset
{
'posts': {
'orderBy': {'createdAt': 'desc'},
'limit': 20, // Maximum 20 results
'offset': 40, // Skip first 40 results (page 3)
}
}InstantDB supports simple offset-based pagination.
// First page
{
'posts': {
'orderBy': {'createdAt': 'desc'},
'limit': 20,
'offset': 0,
}
}
// Next page
{
'posts': {
'orderBy': {'createdAt': 'desc'},
'limit': 20,
'offset': 20,
}
}Pagination Helper
class PaginationHelper {
static Map<String, dynamic> buildQuery({
required String entityType,
required int page,
required int pageSize,
Map<String, dynamic>? where,
Map<String, String>? orderBy,
}) {
return {
entityType: {
if (where != null) 'where': where,
if (orderBy != null) 'orderBy': orderBy,
'limit': pageSize,
'offset': page * pageSize,
}
};
}
}
// Usage
final query = PaginationHelper.buildQuery(
entityType: 'posts',
page: 2, // Third page (0-indexed)
pageSize: 10,
where: {'published': true},
orderBy: {'createdAt': 'desc'},
);Relationships and Includes
Basic Includes
{
'posts': {
'include': {
'author': {}, // Include author for each post
'comments': { // Include comments for each post
'limit': 5,
'orderBy': {'createdAt': 'desc'},
},
}
}
}Nested Includes
{
'posts': {
'include': {
'author': {
'include': {
'profile': {}, // Include author's profile
}
},
'comments': {
'include': {
'author': {}, // Include comment authors
},
'orderBy': {'createdAt': 'asc'},
},
}
}
}Conditional Includes
{
'posts': {
'include': {
'author': {
'where': {'active': true}, // Only include if author is active
},
'comments': {
'where': {'approved': true}, // Only approved comments
'limit': 10,
},
}
}
}Complex Relationship Queries
// Posts with their authors and latest comments
{
'posts': {
'where': {'published': true},
'include': {
'author': {
'include': {
'profile': {},
}
},
'comments': {
'where': {'approved': true},
'orderBy': {'createdAt': 'desc'},
'limit': 3,
'include': {
'author': {
'include': {
'profile': {},
}
}
}
},
'tags': {},
'category': {},
},
'orderBy': {'publishedAt': 'desc'},
'limit': 10,
}
}Lookups
Use lookups to reference entities by attributes other than ID.
lookup() Function
LookupRef lookup(String entityType, String attribute, dynamic value)Parameters:
entityType(String): Type of entity to lookupattribute(String): Attribute to match againstvalue(dynamic): Value to match
Returns: LookupRef - Lookup reference object
Lookup Examples
// Reference user by email instead of ID
{
'posts': {
'where': {
'author': lookup('users', 'email', 'john@example.com'),
}
}
}
// Create post with author lookup
await db.transact([
...db.create('posts', {
'id': db.id(),
'title': 'My Post',
'authorId': lookup('users', 'email', 'author@example.com'),
}),
]);
// Multiple lookups
{
'posts': {
'where': {
'author': lookup('users', 'username', 'john_doe'),
'category': lookup('categories', 'slug', 'technology'),
}
}
}Advanced Query Patterns
Search Functionality
class SearchQueries {
static Map<String, dynamic> searchPosts(String searchTerm) {
return {
'posts': {
'where': {
'\$or': [
{'title': {'\$ilike': '%$searchTerm%'}},
{'content': {'\$ilike': '%$searchTerm%'}},
{'tags': {'\$contains': searchTerm.toLowerCase()}},
]
},
'orderBy': {'relevanceScore': 'desc'},
'limit': 20,
}
};
}
static Map<String, dynamic> searchUsers(String query) {
return {
'users': {
'where': {
'\$or': [
{'name': {'\$ilike': '%$query%'}},
{'username': {'\$ilike': '%$query%'}},
{'email': {'\$like': '%$query%'}},
],
'active': true, // Only active users
},
'orderBy': [
{'verified': 'desc'}, // Verified users first
{'name': 'asc'}, // Then by name
],
'limit': 10,
}
};
}
}Aggregation Queries
// Get user statistics
{
'users': {
'where': {'active': true},
'include': {
'posts': {
'where': {'published': true},
// Count will be included in results
},
'comments': {
'where': {'approved': true},
},
}
}
}
// Posts with comment counts
{
'posts': {
'where': {'published': true},
'include': {
'comments': {
'where': {'approved': true},
},
'likes': {}, // Will include like count
},
'orderBy': {'commentCount': 'desc'},
}
}Time-Based Queries
class TimeQueries {
static Map<String, dynamic> getRecentPosts(Duration duration) {
final since = DateTime.now().subtract(duration).millisecondsSinceEpoch;
return {
'posts': {
'where': {
'createdAt': {'\$gte': since},
'published': true,
},
'orderBy': {'createdAt': 'desc'},
}
};
}
static Map<String, dynamic> getPostsInRange(DateTime start, DateTime end) {
return {
'posts': {
'where': {
'createdAt': {
'\$gte': start.millisecondsSinceEpoch,
'\$lt': end.millisecondsSinceEpoch,
}
},
'orderBy': {'createdAt': 'asc'},
}
};
}
static Map<String, dynamic> getTrendingPosts() {
final lastWeek = DateTime.now().subtract(Duration(days: 7)).millisecondsSinceEpoch;
return {
'posts': {
'where': {
'createdAt': {'\$gte': lastWeek},
'viewCount': {'\$gt': 100},
},
'orderBy': [
{'viewCount': 'desc'},
{'likeCount': 'desc'},
{'commentCount': 'desc'},
],
'limit': 20,
}
};
}
}Conditional Queries
class ConditionalQueries {
static Map<String, dynamic> buildUserQuery({
String? role,
bool? active,
DateTime? createdAfter,
List<String>? excludeIds,
}) {
final where = <String, dynamic>{};
if (role != null) {
where['role'] = role;
}
if (active != null) {
where['active'] = active;
}
if (createdAfter != null) {
where['createdAt'] = {'\$gte': createdAfter.millisecondsSinceEpoch};
}
if (excludeIds != null && excludeIds.isNotEmpty) {
where['id'] = {'\$nin': excludeIds};
}
return {
'users': {
if (where.isNotEmpty) 'where': where,
'orderBy': {'createdAt': 'desc'},
}
};
}
}
// Usage
final query = ConditionalQueries.buildUserQuery(
role: 'user',
active: true,
createdAfter: DateTime.now().subtract(Duration(days: 30)),
excludeIds: ['blocked-user-1', 'blocked-user-2'],
);Query Optimization
Efficient Filtering
// ✅ Good: Filter at database level
{
'posts': {
'where': {
'published': true,
'authorId': currentUserId,
},
'limit': 10,
}
}
// ❌ Avoid: Filter in application
{
'posts': {} // Gets all posts, then filter in Dart code
}Index-Friendly Queries
// ✅ Good: Use indexed fields for where conditions
{
'posts': {
'where': {
'createdAt': {'\$gte': timestamp}, // Usually indexed
'published': true, // Simple equality
}
}
}
// ❌ Less efficient: Complex string operations
{
'posts': {
'where': {
'content': {'\$regex': 'complex.*pattern'}, // Expensive
}
}
}Limit Result Sets
// Always use appropriate limits
{
'posts': {
'where': {'published': true},
'limit': 20, // Prevent loading too much data
'orderBy': {'createdAt': 'desc'},
}
}Error Handling
Handle query errors appropriately:
Future<List<Map<String, dynamic>>> safeQuery(Map<String, dynamic> query) async {
try {
final result = await db.queryOnce(query);
if (result.error != null) {
print('Query error: ${result.error}');
return [];
}
// Extract first entity type from results
final entityType = query.keys.first;
return (result.data?[entityType] as List? ?? [])
.cast<Map<String, dynamic>>();
} on InstantException catch (e) {
print('InstantDB error: ${e.message}');
return [];
} catch (e) {
print('Unexpected error: $e');
return [];
}
}Query Builder Helper
Create a query builder for complex queries:
class QueryBuilder {
final Map<String, dynamic> _query = {};
QueryBuilder entity(String entityType) {
_query[entityType] = <String, dynamic>{};
return this;
}
QueryBuilder where(Map<String, dynamic> conditions) {
final entityType = _query.keys.last;
_query[entityType]['where'] = conditions;
return this;
}
QueryBuilder orderBy(Map<String, String> ordering) {
final entityType = _query.keys.last;
_query[entityType]['orderBy'] = ordering;
return this;
}
QueryBuilder limit(int count) {
final entityType = _query.keys.last;
_query[entityType]['limit'] = count;
return this;
}
QueryBuilder offset(int count) {
final entityType = _query.keys.last;
_query[entityType]['offset'] = count;
return this;
}
QueryBuilder include(Map<String, dynamic> includes) {
final entityType = _query.keys.last;
_query[entityType]['include'] = includes;
return this;
}
Map<String, dynamic> build() => Map<String, dynamic>.from(_query);
}
// Usage
final query = QueryBuilder()
.entity('posts')
.where({
'published': true,
'createdAt': {'\$gte': DateTime.now().subtract(Duration(days: 7)).millisecondsSinceEpoch},
})
.orderBy({'createdAt': 'desc'})
.limit(10)
.include({
'author': {},
'comments': {'limit': 5},
})
.build();
final result = await db.queryOnce(query);Next Steps
Explore related APIs:
- InstantDB Core - Main database class and initialization
- Transactions API - Creating and updating data
- Presence API - Real-time collaboration queries
- Flutter Widgets - Reactive query widgets
- Types Reference - Query result types and structures