Queries API
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
subscribeQuery()
Create a reactive query that automatically updates when data changes.
Signal<QueryResult> subscribeQuery(Map<String, dynamic> query)
Parameters:
query
(Map<String, dynamic>
): InstaQL query object
Returns: Signal<QueryResult>
- Reactive signal containing query results
Example:
final todosSignal = db.subscribeQuery({ 'todos': { 'where': {'completed': false}, 'orderBy': {'createdAt': 'desc'}, 'limit': 10, },});
// Use in reactive widgetsWatch((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 'name': {'\$ilike': '%john%'}, // Case-insensitive like 'username': {'\$startsWith': 'admin_'}, // Starts with 'bio': {'\$endsWith': '...'}, // Ends with 'status': {'\$regex': '^(active|pending)\$'}, // Regular expression } }}
Array Operators
{ 'posts': { 'where': { 'tags': {'\$contains': 'flutter'}, // Array contains value 'categories': {'\$containsAny': ['tech', 'programming']}, // Contains any 'skills': {'\$size': {'\$gte': 3}}, // Array size condition 'permissions': {'\$in': ['read', 'write']}, // Value in array 'blockedUsers': {'\$nin': ['user-123']}, // Value not in array } }}
Existence Operators
{ 'users': { 'where': { 'profilePicture': {'\$exists': true}, // Field exists 'deletedAt': {'\$isNull': true}, // Field is null 'verifiedAt': {'\$isNotNull': true}, // Field is not null } }}
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 conditionsString 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) }}
Cursor-Based Pagination
// First page{ 'posts': { 'orderBy': {'createdAt': 'desc'}, 'limit': 20, }}
// Next page using cursor{ 'posts': { 'orderBy': {'createdAt': 'desc'}, 'limit': 20, 'before': lastPostCreatedAt, // Get posts before this timestamp }}
// Previous page{ 'posts': { 'orderBy': {'createdAt': 'desc'}, 'limit': 20, 'after': firstPostCreatedAt, // Get posts after this timestamp }}
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, } }; }}
// Usagefinal 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 lookupawait 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'}, } }; }}
// Usagefinal 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);}
// Usagefinal 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