Write Firebase Cloud Functions in Dart with full type safety and performance.
This package provides a complete Dart implementation of Firebase Cloud Functions with support for:
| Trigger Type | Status | Functions |
|---|---|---|
| HTTPS | âś… Complete | onRequest, onCall, onCallWithData |
| Pub/Sub | âś… Complete | onMessagePublished |
| Firestore | âś… Complete | onDocumentCreated, onDocumentUpdated, onDocumentDeleted, onDocumentWritten |
| Realtime Database | âś… Complete | onValueCreated, onValueUpdated, onValueDeleted, onValueWritten |
| Firebase Alerts | âś… Complete | Crashlytics, Billing, Performance alerts |
| Identity Platform | âś… Complete | beforeUserCreated, beforeUserSignedIn (+ beforeEmailSent, beforeSmsSent*) |
- Type-safe: Leverage Dart's strong type system with typed callable functions and CloudEvents
- Fast: Compiled Dart code with efficient Shelf HTTP server
- Familiar API: Similar to Firebase Functions Node.js SDK v2
- Streaming: Server-Sent Events (SSE) support for callable functions
- Parameterized: Deploy-time configuration with
defineString,defineInt,defineBoolean - Conditional Config: CEL expressions for environment-based options
- Error Handling: Built-in typed error classes matching the Node.js SDK
- Hot Reload: Fast development with build_runner watch
- Dart SDK >=3.0.0
- Custom Firebase CLI with Dart runtime support:
git clone -b @invertase/dart https://github.com/invertase/firebase-tools.git
cd firebase-tools
npm install
npm run build
npm linkAdd to your pubspec.yaml:
dependencies:
firebase_functions:
path: ../firebase-functions-dartimport 'package:firebase_functions/firebase_functions.dart';
void main(List<String> args) {
fireUp(args, (firebase) {
// Register your functions here
});
}firebase.https.onRequest(
name: 'hello',
(request) async {
return Response.ok('Hello from Dart!');
},
);firebase.https.onCall(
name: 'greet',
(request, response) async {
final data = request.data as Map<String, dynamic>?;
final name = data?['name'] ?? 'World';
return CallableResult({'message': 'Hello, $name!'});
},
);firebase.https.onCallWithData<GreetRequest, GreetResponse>(
name: 'greetTyped',
fromJson: GreetRequest.fromJson,
(request, response) async {
return GreetResponse(message: 'Hello, ${request.data.name}!');
},
);firebase.https.onCall(
name: 'countdown',
options: const CallableOptions(
heartBeatIntervalSeconds: HeartBeatIntervalSeconds(5),
),
(request, response) async {
if (request.acceptsStreaming) {
for (var i = 10; i >= 0; i--) {
await response.sendChunk({'count': i});
await Future.delayed(Duration(milliseconds: 100));
}
}
return CallableResult({'message': 'Countdown complete!'});
},
);firebase.https.onCall(
name: 'divide',
(request, response) async {
final data = request.data as Map<String, dynamic>?;
final a = data?['a'] as num?;
final b = data?['b'] as num?;
if (a == null || b == null) {
throw InvalidArgumentError('Both "a" and "b" are required');
}
if (b == 0) {
throw FailedPreconditionError('Cannot divide by zero');
}
return CallableResult({'result': a / b});
},
);Available error types: InvalidArgumentError, FailedPreconditionError, NotFoundError, AlreadyExistsError, PermissionDeniedError, ResourceExhaustedError, UnauthenticatedError, UnavailableError, InternalError, DeadlineExceededError, CancelledError.
firebase.pubsub.onMessagePublished(
topic: 'my-topic',
(event) async {
final message = event.data;
print('ID: ${message?.messageId}');
print('Data: ${message?.textData}');
print('Attributes: ${message?.attributes}');
},
);// Document created
firebase.firestore.onDocumentCreated(
document: 'users/{userId}',
(event) async {
final data = event.data?.data();
print('Created: users/${event.params['userId']}');
print('Name: ${data?['name']}');
},
);
// Document updated
firebase.firestore.onDocumentUpdated(
document: 'users/{userId}',
(event) async {
final before = event.data?.before?.data();
final after = event.data?.after?.data();
print('Before: $before');
print('After: $after');
},
);
// Document deleted
firebase.firestore.onDocumentDeleted(
document: 'users/{userId}',
(event) async {
final data = event.data?.data();
print('Deleted data: $data');
},
);
// All write operations
firebase.firestore.onDocumentWritten(
document: 'users/{userId}',
(event) async {
final before = event.data?.before?.data();
final after = event.data?.after?.data();
// Determine operation type
if (before == null && after != null) print('CREATE');
if (before != null && after != null) print('UPDATE');
if (before != null && after == null) print('DELETE');
},
);
// Nested collections
firebase.firestore.onDocumentCreated(
document: 'posts/{postId}/comments/{commentId}',
(event) async {
print('Post: ${event.params['postId']}');
print('Comment: ${event.params['commentId']}');
},
);// Value created
firebase.database.onValueCreated(
ref: 'messages/{messageId}',
(event) async {
final data = event.data?.val();
print('Created: ${event.params['messageId']}');
print('Data: $data');
print('Instance: ${event.instance}');
},
);
// Value updated
firebase.database.onValueUpdated(
ref: 'messages/{messageId}',
(event) async {
final before = event.data?.before?.val();
final after = event.data?.after?.val();
print('Before: $before');
print('After: $after');
},
);
// Value deleted
firebase.database.onValueDeleted(
ref: 'messages/{messageId}',
(event) async {
final data = event.data?.val();
print('Deleted: $data');
},
);
// All write operations
firebase.database.onValueWritten(
ref: 'users/{userId}/status',
(event) async {
final before = event.data?.before;
final after = event.data?.after;
if (before == null || !before.exists()) print('CREATE');
else if (after == null || !after.exists()) print('DELETE');
else print('UPDATE');
},
);// Crashlytics fatal issues
firebase.alerts.crashlytics.onNewFatalIssuePublished(
(event) async {
final issue = event.data?.payload.issue;
print('Issue: ${issue?.title}');
print('App: ${event.appId}');
},
);
// Billing plan updates
firebase.alerts.billing.onPlanUpdatePublished(
(event) async {
final payload = event.data?.payload;
print('New Plan: ${payload?.billingPlan}');
print('Updated By: ${payload?.principalEmail}');
},
);
// Performance threshold alerts
firebase.alerts.performance.onThresholdAlertPublished(
options: const AlertOptions(appId: '1:123456789:ios:abcdef'),
(event) async {
final payload = event.data?.payload;
print('Metric: ${payload?.metricType}');
print('Threshold: ${payload?.thresholdValue}');
print('Actual: ${payload?.violationValue}');
},
);// Before user created
firebase.identity.beforeUserCreated(
options: const BlockingOptions(idToken: true, accessToken: true),
(AuthBlockingEvent event) async {
final user = event.data;
// Block certain email domains
if (user?.email?.endsWith('@blocked.com') ?? false) {
throw PermissionDeniedError('Email domain not allowed');
}
// Set custom claims
if (user?.email?.endsWith('@admin.com') ?? false) {
return const BeforeCreateResponse(
customClaims: {'admin': true},
);
}
return null;
},
);
// Before user signed in
firebase.identity.beforeUserSignedIn(
options: const BlockingOptions(idToken: true),
(AuthBlockingEvent event) async {
return BeforeSignInResponse(
sessionClaims: {
'lastLogin': DateTime.now().toIso8601String(),
'signInIp': event.ipAddress,
},
);
},
);Note:
beforeEmailSentandbeforeSmsSentare also available but cannot be tested with the Firebase Auth emulator (emulator only supportsbeforeUserCreatedandbeforeUserSignedIn). They work in production deployments.
final welcomeMessage = defineString(
'WELCOME_MESSAGE',
ParamOptions(
defaultValue: 'Hello from Dart!',
label: 'Welcome Message',
description: 'The greeting message returned by the function',
),
);
final minInstances = defineInt(
'MIN_INSTANCES',
ParamOptions(defaultValue: 0),
);
final isProduction = defineBoolean(
'IS_PRODUCTION',
ParamOptions(defaultValue: false),
);firebase.https.onRequest(
name: 'hello',
(request) async {
return Response.ok(welcomeMessage.value());
},
);firebase.https.onRequest(
name: 'configured',
options: HttpsOptions(
minInstances: DeployOption.param(minInstances),
),
handler,
);firebase.https.onRequest(
name: 'api',
options: HttpsOptions(
// 2GB in production, 512MB in development
memory: Memory.expression(isProduction.thenElse(2048, 512)),
),
(request) async {
final env = isProduction.value() ? 'production' : 'development';
return Response.ok('Running in $env mode');
},
);Your firebase.json must specify the Dart runtime:
{
"functions": [
{
"source": ".",
"codebase": "default",
"runtime": "dart3"
}
],
"emulators": {
"functions": { "port": 5001 },
"firestore": { "port": 8080 },
"database": { "port": 9000 },
"auth": { "port": 9099 },
"pubsub": { "port": 8085 },
"ui": { "enabled": true, "port": 4000 }
}
}firebase emulators:startdart run build_runner buildRun all tests:
dart testRun specific test suites:
# Unit tests only
dart test --exclude-tags=snapshot,integration
# Builder tests
dart run build_runner build --delete-conflicting-outputs
dart test test/builder/
# Snapshot tests (compare with Node.js SDK)
dart test test/snapshots/See Testing Guide for more details.
Apache 2.0