Skip to Content

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 object
  • syncedOnly (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 type
  • error (String?): Error message if query failed
  • isLoading (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 lookup
  • attribute (String): Attribute to match against
  • value (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: