Flutter SDK
Flutter / Dart Quick Start
WowSQL Flutter SDK — Get started in minutes
Get your API keys: In your WowSQL project dashboard, go to Settings → API to find your anon key (wowsql_anon_...) and service role key (wowsql_service_...). The anon key is safe for client-side use (respects Row Level Security). The service role key bypasses RLS and should only be used server-side.
Installation
Official package: View on pub.dev →
dart pub add wowsql
Initialize Client
import 'package:wowsql/wowsql.dart';
void main() async {
// Initialize client with your anon key (respects Row Level Security)
// URL resolves to: https://myproject.wowsqlconnect.com
// SDK talks to PostgREST at /rest/v1/
final client = WowSQLClient(
projectUrl: 'myproject',
apiKey: 'wowsql_anon_your_key_here',
);
// Or with full URL
final clientFull = WowSQLClient(
projectUrl: 'https://myproject.wowsqlconnect.com',
apiKey: 'wowsql_anon_your_key_here',
);
// Always use select() before get()/execute()
final users = await client.table('users').select('*').get();
for (final user in users.data) {
print(user['name']);
}
}
Query Data
Basic Queries
// Get all records
final users = await client.table('users').select('*').get();
// With filters
final adults = await client.table('users').select('*').gte('age', 18).get();
// Select specific columns
final names = await client.table('users').select('id', 'name').get();
Advanced Queries
// Multiple filters with sorting
final activeUsers = await client.table('users')
.select('*')
.eq('status', 'active')
.gt('age', 18)
.order('created_at', 'desc')
.limit(20)
.offset(0)
.get();
print('Found ${activeUsers.count} users');
for (final user in activeUsers.data) {
print(user['name']);
}
Filter Helper Methods
// Using helper methods: eq, gt, like, in, between
final users = await client.table('users')
.eq('status', 'active')
.gt('age', 18)
.like('email', '%@gmail.com')
.in_('role', ['admin', 'moderator'])
.between('created_at', '2024-01-01', '2024-12-31')
.get();
GROUP BY and Aggregates
Basic GROUP BY
final result = await client.table('products')
.select('category', 'COUNT(*) as count', 'AVG(price) as avg_price')
.groupBy('category')
.get();
final result2 = await client.table('orders')
.select('DATE(created_at) as date', 'COUNT(*) as orders', 'SUM(total) as revenue')
.groupBy('DATE(created_at)')
.orderBy('date', 'desc')
.get();
HAVING Clause
final result = await client.table('products')
.select('category', 'COUNT(*) as count')
.groupBy('category')
.having('COUNT(*)', 'gt', 10)
.get();
Multiple ORDER BY
final result = await client.table('products')
.select('*')
.orderByMultiple([
OrderByItem(column: 'category', direction: SortDirection.asc),
OrderByItem(column: 'price', direction: SortDirection.desc),
])
.get();
CRUD Operations
Create Record
final result = await client.table('users').create({
'name': 'John Doe',
'email': 'john@example.com',
'age': 30,
});
print('Created user with ID: ${result['id']}');
Update Record
await client.table('users').update(123, {'email': 'newemail@example.com'});
print('User updated successfully');
Delete Record
await client.table('users').delete(123);
print('User deleted successfully');
Get Single Record by ID
final user = await client.table('users').getById(123);
print('User: ${user['name']}');
Get First Record
final user = await client.table('users').eq('status', 'active').first();
if (user != null) {
print('First active user: ${user['name']}');
}
Filter Operators
Available operators: eq, neq, gt, gte, lt, lte, like, is, in, not_in, between, not_between. Use .eq('col', value), .filter('col', 'op', value), etc.
Error Handling
try {
final users = await client.table('users').select('*').get();
print('Success: ${users.data.length} users');
} on WowSQLException catch (e) {
if (e.statusCode == 401) print('Authentication failed — check your API key');
else if (e.statusCode == 404) print('Table or resource not found');
else print('Error (${e.statusCode}): ${e.message}');
}
Authentication
Authentication endpoints are served at /auth/v1/. Use your anon key for client-side auth operations.
Sign Up & Sign In
// Auth endpoints: POST /auth/v1/signup, POST /auth/v1/login
// Header: apikey: wowsql_anon_your_key_here
final auth = WowSQLAuth(projectUrl: 'myproject', apiKey: 'wowsql_anon_your_key_here');
final result = await auth.signUp(
email: 'user@example.com',
password: 'SecurePassword123',
fullName: 'John Doe',
);
print('Access token: ${result['access_token']}');
final login = await auth.signIn(
email: 'user@example.com',
password: 'SecurePassword123',
);
print('User ID: ${login['user']['id']}');
Password Reset
await auth.forgotPassword(email: 'user@example.com');
await auth.resetPassword(token: 'reset_token_from_email', newPassword: 'NewSecurePassword123');
OTP Authentication
await auth.sendOtp(email: 'user@example.com', purpose: 'login');
final result = await auth.verifyOtp(
email: 'user@example.com',
otp: '123456',
purpose: 'login',
);
print('Logged in: ${result.user.id}');
Magic Link & Email Verification
await auth.sendMagicLink(email: 'user@example.com', purpose: 'login');
await auth.verifyEmail(token: 'verification_token_from_email');
await auth.resendVerification(email: 'user@example.com');
OAuth Authentication
Setup checklist — do this once per provider, per project:
1. Dashboard → Project → Auth → Providers: enable the provider, paste Client ID and Client Secret (no leading/trailing spaces).
2. Copy the Redirect URI shown: https://<slug>.wowsqlconnect.com/auth/v1/oauth/<provider>/callback and register it in your provider's developer console.
3. The callback URL is handled entirely by WowSQL — your app does not send an API key on it. WowSQL exchanges the code and redirects to your frontend_redirect_uri with ?access_token=&refresh_token=.
// ── Step 1: Get the authorization URL ───────────────────────────────────────
final oauthUrl = await auth.getOAuthAuthorizationUrl(
provider: 'google',
redirectUri: 'https://yourapp.com/auth/callback',
);
// Launch the URL in a browser / in-app WebView:
// launchUrl(Uri.parse(oauthUrl.authorizationUrl));
// Google calls: https://<slug>.wowsqlconnect.com/auth/v1/oauth/google/callback
// WowSQL exchanges the code and redirects to your redirectUri.
// ── Step 2: App — handle the redirect ────────────────────────────────────────
// WowSQL redirects to: https://yourapp.com/auth/callback?access_token=...&refresh_token=...
// Use flutter_web_auth_2 or a deep-link handler to intercept the URI and extract tokens.
// ── (Advanced) Manual code exchange — only if you handle the callback yourself
final result = await auth.exchangeOAuthCallback(
provider: 'google',
code: 'authorization_code_from_callback',
redirectUri: 'https://yourapp.com/auth/callback', // must match step 1
);
print('OAuth login successful: \${result.user.id}');
Storage Operations
Initialize Storage
// Storage endpoints are at /storage/v1/
final storage = WowSQLStorage(
projectSlug: 'myproject',
apiKey: 'wowsql_anon_your_key_here',
);
File Operations
// Upload file — POST /storage/v1/object/{bucket}/{path}
final result = await storage.uploadFromPath('path/to/document.pdf', 'uploads/document.pdf', folder: 'documents');
print('Uploaded: ${result["file_key"]}');
// Upload from bytes
import 'dart:typed_data';
final bytes = Uint8List.fromList('Hello, WowSQL!'.codeUnits);
final upload = await storage.upload(bytes, 'hello.txt', folder: 'uploads', contentType: 'text/plain');
print('Uploaded: ${upload.key}');
// Download file — GET /storage/v1/object/{bucket}/{path}
final urlData = await storage.getFileUrl('uploads/document.pdf', expiresIn: Duration(hours: 1));
print('File URL: ${urlData["file_url"]}');
print('Bucket: ${urlData["bucket_name"]}');
print('Expires at: ${urlData["expires_at"]}');
// Get presigned URL (simple)
final downloadUrl = await storage.getPresignedUrl('uploads/document.pdf', expiresIn: Duration(hours: 1));
final uploadUrl = await storage.getPresignedUrl('uploads/new-file.pdf', expiresIn: Duration(hours: 1), operation: 'put_object');
// List files — POST /storage/v1/object/list/{bucket}
final files = await storage.listFiles(prefix: 'uploads/', maxKeys: 100);
for (final file in files) {
print('${file.key}: ${file.size} bytes');
}
// Delete file
await storage.deleteFile('uploads/document.pdf');
// Check quota
final quota = await storage.getQuota();
print('Used: ${quota.storageUsedGb.toStringAsFixed(2)} GB');
print('Total: ${quota.storageQuotaGb.toStringAsFixed(2)} GB');
print('Usage: ${quota.usagePercentage.toStringAsFixed(1)}%');
Download or View File
Via REST: use GET /storage/v1/object/{bucket}/{path} for download, or GET /storage/v1/object/public/{bucket}/{path} for public file access.
Schema DDL (WOWSQLSchema)
Requires service role key. createTable requires primaryKey to name a column whose type is UUID. Use autoIncrement: true on that column for DEFAULT gen_random_uuid(). Raw CREATE TABLE via execute SQL cannot use SERIAL PRIMARY KEY (or similar integer PK). Existing tables are unchanged.
final schema = WowSQLSchema(
projectUrl: 'myproject',
apiKey: 'wowsql_service_your_key_here',
);
await schema.createTable(
name: 'items',
columns: [
ColumnDefinition(name: 'id', type: 'UUID', autoIncrement: true),
ColumnDefinition(name: 'title', type: 'TEXT'),
],
primaryKey: 'id',
);
Example API Call (REST)
The SDK wraps PostgREST. Here's what the underlying HTTP calls look like:
Query (GET)
GET https://myproject.wowsqlconnect.com/rest/v1/users?select=id,email,name&status=eq.active&order=created_at.desc&limit=10
Headers:
apikey: wowsql_anon_your_key_here
Content-Type: application/json
Insert (POST)
POST https://myproject.wowsqlconnect.com/rest/v1/users
Headers:
apikey: wowsql_anon_your_key_here
Content-Type: application/json
Prefer: return=representation
Body:
{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
Update (PATCH)
PATCH https://myproject.wowsqlconnect.com/rest/v1/users?id=eq.123
Headers:
apikey: wowsql_anon_your_key_here
Content-Type: application/json
Prefer: return=representation
Body:
{
"email": "newemail@example.com"
}
Delete (DELETE)
DELETE https://myproject.wowsqlconnect.com/rest/v1/users?id=eq.123
Headers:
apikey: wowsql_anon_your_key_here