Basic Queries
InstantDB uses InstaQL, a simple yet powerful query language designed for real-time applications. All queries are reactive by default, meaning your UI automatically updates when data changes.
Simple Queries
Fetch All Records
Query all records of a specific entity type:
// Get all todosfinal todosQuery = db.query({'todos': {}});
// Access the dataWatch((context) { final result = todosQuery.value;
if (result.isLoading) { return const CircularProgressIndicator(); }
if (result.hasError) { return Text('Error: ${result.error}'); }
final todos = result.data!['todos'] as List; return ListView.builder( itemCount: todos.length, itemBuilder: (context, index) => TodoTile(todo: todos[index]), );});
Filter with Where Clauses
Filter data using where conditions:
// Get completed todos onlyfinal completedTodos = db.query({ 'todos': { 'where': {'completed': true}, },});
// Get todos created todayfinal today = DateTime.now();final startOfDay = DateTime(today.year, today.month, today.day);
final todaysTodos = db.query({ 'todos': { 'where': { 'createdAt': {'\$gte': startOfDay.millisecondsSinceEpoch}, }, },});
Sorting and Limiting
Control the order and number of results:
// Get latest 10 todos, sorted by creation datefinal latestTodos = db.query({ 'todos': { '\$': { 'order': {'createdAt': 'desc'}, 'limit': 10, }, },});
// Multiple sort fieldsfinal sortedTodos = db.query({ 'todos': { '\$': { 'order': [ {'priority': 'desc'}, {'createdAt': 'asc'}, ], }, },});
Query Operators
Comparison Operators
Operator | Description | Example |
---|---|---|
\$eq | Equal (default) | {'status': 'active'} |
\$neq | Not equal | {'status': {'\$neq': 'deleted'}} |
\$gt | Greater than | {'score': {'\$gt': 100}} |
\$gte | Greater than or equal | {'age': {'\$gte': 18}} |
\$lt | Less than | {'price': {'\$lt': 50}} |
\$lte | Less than or equal | {'quantity': {'\$lte': 10}} |
Array Operators
Operator | Description | Example |
---|---|---|
\$in | Value in array | {'category': {'\$in': ['work', 'personal']}} |
\$nin | Value not in array | {'status': {'\$nin': ['deleted', 'archived']}} |
String Operators
Operator | Description | Example |
---|---|---|
\$contains | Contains substring | {'title': {'\$contains': 'urgent'}} |
\$startsWith | Starts with | {'email': {'\$startsWith': 'admin'}} |
\$endsWith | Ends with | {'filename': {'\$endsWith': '.pdf'}} |
React-Style Query Syntax
InstantDB Flutter supports React-style query syntax for compatibility:
final query = db.query({ 'todos': { '\$': { 'where': {'completed': false}, 'order': {'createdAt': 'desc'}, 'limit': 20, }, },});
final query = db.query({ 'todos': { 'where': {'completed': false}, 'orderBy': [{'createdAt': 'desc'}], 'limit': 20, },});
Both syntaxes are supported and can be used interchangeably.
Reactive Queries
Using InstantBuilder
The recommended way to use queries in widgets:
InstantBuilder( query: { 'todos': { 'where': {'userId': currentUserId}, }, }, builder: (context, result) { if (result.isLoading) { return const CircularProgressIndicator(); }
if (result.hasError) { return Text('Error: ${result.error}'); }
final todos = result.data!['todos'] as List; return TodosList(todos: todos); },)
Using InstantBuilderTyped
For better type safety, use the typed version:
InstantBuilderTyped<List<Map<String, dynamic>>>( query: {'todos': {}}, transformer: (data) { final todos = (data['todos'] as List).cast<Map<String, dynamic>>(); // Apply client-side sorting or filtering if needed todos.sort((a, b) => b['createdAt'].compareTo(a['createdAt'])); return todos; }, builder: (context, todos) { return ListView.builder( itemCount: todos.length, itemBuilder: (context, index) => TodoTile(todo: todos[index]), ); },)
One-time Queries
For non-reactive queries that execute once:
// Execute query once and get resultfinal result = await db.queryOnce({'todos': {}});
if (result.hasData) { final todos = result.data!['todos'] as List; print('Found ${todos.length} todos');}
Complex Queries
Multiple Conditions
Combine multiple where conditions:
final complexQuery = db.query({ 'todos': { 'where': { 'completed': false, 'priority': {'\$in': ['high', 'urgent']}, 'dueDate': {'\$lte': DateTime.now().millisecondsSinceEpoch}, 'assignee': {'\$neq': null}, }, },});
Nested Conditions
Use logical operators for complex conditions:
final nestedQuery = db.query({ 'todos': { 'where': { '\$or': [ {'priority': 'urgent'}, { '\$and': [ {'priority': 'high'}, {'dueDate': {'\$lte': DateTime.now().millisecondsSinceEpoch}}, ], }, ], }, },});
Query Performance
Indexing
Ensure your frequently queried fields are indexed in your InstantDB schema:
// This will perform better if 'userId' is indexedfinal userTodos = db.query({ 'todos': { 'where': {'userId': currentUserId}, },});
Pagination
Use limit and offset for large datasets:
final page1 = db.query({ 'todos': { '\$': { 'limit': 20, 'offset': 0, }, },});
final page2 = db.query({ 'todos': { '\$': { 'limit': 20, 'offset': 20, }, },});
Error Handling
Handle query errors gracefully:
InstantBuilder( query: {'todos': {}}, errorBuilder: (context, error) { return Column( children: [ const Icon(Icons.error, color: Colors.red), Text('Failed to load todos: $error'), ElevatedButton( onPressed: () { // Retry logic }, child: const Text('Retry'), ), ], ); }, builder: (context, result) { // Success case final todos = result.data!['todos'] as List; return TodosList(todos: todos); },)
Next Steps
Learn more about advanced querying features:
- Relationships - Query related data
- Advanced Queries - Complex patterns and optimization
- Real-time Updates - Understanding live data
- Schema Validation - Type-safe queries