With Flutter's cloud_functions plugin, how do you call a callable Cloud Function and handle the errors it can return?
answer
- httpsCallable(name) returns a callable
- call<T>() -> HttpsCallableResult.data
- auth token attached automatically
- FirebaseFunctionsException: code, message, details
- 60 s client timeout -> deadline-exceeded
basics
~10 sGet a reference with FirebaseFunctions.instance.httpsCallable('name'), await call(data), and read result.data; failures throw FirebaseFunctionsException, whose code, message and details come from the function or the client, such as deadline-exceeded after the 60-second default timeout.
solid answer
~40 s`FirebaseFunctions.instance.httpsCallable('validateCoupon')` returns an `HttpsCallable`. Awaiting `callable.call<T>(data)`, or `callable(data)`, sends the payload and returns an `HttpsCallableResult<T>` whose `data` holds the function's return value. The payload may be `null`, a `String`, a `num`, a `bool`, or a `List` or `Map` of those, with `String` keys; typed-data lists are converted to lists. If a user is signed in with Firebase Auth, their ID token is attached automatically, so the function knows who is calling without you adding headers. Errors arrive as `FirebaseFunctionsException`, a `FirebaseException` with a `code` such as `invalid-argument`, `unauthenticated`, `not-found` or `deadline-exceeded`, a `message`, and optional `details` the function attached. `HttpsCallableOptions(timeout: ...)` sets the client timeout, 60 seconds by default. Catch `FirebaseFunctionsException` specifically and map codes to UI states.
code
dart · 33 linesimport 'package:cloud_functions/cloud_functions.dart';
sealed class CouponResult {}
class CouponApplied extends CouponResult {
CouponApplied(this.discount);
final num discount;
}
class CouponRejected extends CouponResult {
CouponRejected(this.reason);
final String reason;
}
Future<CouponResult> validateCoupon(String code, double cartTotal) async {
final callable = FirebaseFunctions.instance.httpsCallable(
'validateCoupon',
options: HttpsCallableOptions(timeout: const Duration(seconds: 15)),
);
try {
final result = await callable.call<Map<Object?, Object?>>({
'code': code,
'cartTotal': cartTotal,
});
return CouponApplied(result.data['discount'] as num);
} on FirebaseFunctionsException catch (e) {
return switch (e.code) {
'invalid-argument' || 'failed-precondition' =>
CouponRejected(e.message ?? 'Coupon not valid'),
'unauthenticated' => CouponRejected('Sign in to use coupons'),
'deadline-exceeded' => CouponRejected('Timed out, try again'),
_ => CouponRejected('Something went wrong (${e.code})'),
};
}
}go deeper
Know the three steps, httpsCallable, call and result.data, and that failures throw FirebaseFunctionsException with a code and a message.
Explain which payload types are allowed, that the signed-in user's ID token is attached automatically, the 60-second client timeout, and what details carries.
Show how you map codes to UI states, why internal hides server errors, and why a client timeout demands idempotent state-changing functions.
Decide what belongs in callables versus direct database access or a REST backend, weighing trust boundaries, latency, cost and how error contracts are versioned across app releases.
## What a callable is A **callable function** is a Cloud Function exposed through Firebase's callable protocol: an HTTPS endpoint that the client SDK calls with a JSON payload and authentication handled for it. In Flutter the client is the **cloud_functions** plugin. You do not build URLs or headers yourself. You name the function, pass data, and get data or a typed exception back. Writing and deploying the function itself is server-side work, covered elsewhere. ## The call path 1. `FirebaseFunctions.instance` returns the plugin for the default app and the default region, `us-central1`. 2. `httpsCallable('validateCoupon', options: HttpsCallableOptions(...))` returns an `HttpsCallable` reference. It is cheap to create and makes no network call yet. 3. `await callable.call<Map<String, dynamic>>({'code': 'SPRING10', 'cartTotal': 42.5})` sends the request. `HttpsCallable` defines `call`, so `callable({...})` works too. 4. The result is an `HttpsCallableResult<T>`. Read `result.data`. The generic parameter only casts what the platform decoded, so nested maps may still need converting from `Map<Object?, Object?>` before use. **Payload types:** `null`, `String`, `num`, `bool`, and `List` or `Map` values made of these, with `String` map keys. The doc comment omits `bool`, but the debug assertion accepts it. Typed-data lists such as `Uint8List` are converted to plain lists. Custom classes fail the debug assertion, so convert them with your `toJson` first. **Authentication:** if a Firebase Auth user is signed in, their ID token is attached to every call automatically. The function reads the caller's identity from its request context. There is nothing to set on the client. ## Errors: FirebaseFunctionsException Every failure surfaces as `FirebaseFunctionsException`, a subclass of `FirebaseException` with plugin `firebase_functions`: - `code`: a lower-case, hyphenated status such as `invalid-argument`, `failed-precondition`, `unauthenticated`, `permission-denied`, `not-found`, `resource-exhausted`, `deadline-exceeded`, `unavailable` or `internal`. Android maps native codes to the same spelling, lowercasing with `Locale.ROOT` since cloud_functions 6.5.0. - `message`: the message the function threw, or a client-side description. - `details`: optional structured data the function attached to its error, for example which coupon rule failed. | Code | Typical cause in the coupon flow | UI response | |---|---|---| | `invalid-argument` | function rejected the code format | show a field error | | `failed-precondition` | coupon expired or cart too small | explain using `details` | | `unauthenticated` | no signed-in user and function requires one | prompt sign-in | | `deadline-exceeded` | client timeout reached | offer retry | | `not-found` | wrong function name or region | a bug: log it | | `internal` | unhandled error in the function | generic message and log | An unexpected exception inside the function reaches the client as `internal`, with no detail, by design. Only errors the function deliberately throws carry meaningful codes and details. ## Timeouts `HttpsCallableOptions` has a `timeout` that defaults to **60 seconds**. When it elapses, the client throws `deadline-exceeded`. That does not prove the function stopped: it may still finish on the server. Design calls that change state, such as redeeming a coupon, to be safe to retry. Other options include `limitedUseAppCheckToken` (default `false`) and `webAbortSignal` for cancelling streaming calls on web. ## Streaming callables `callable.stream<T, R>(input)` returns a `Stream<StreamResponse<T, R>>` for functions that send partial results. It emits `Chunk` values followed by a final `Result`. Most coupon-style calls do not need it. ## Practical rules - Catch `FirebaseFunctionsException` before a generic `catch`, and switch on `code`. - Never trust a coupon's validity on the client. The callable exists so the server decides. - Do not wrap callables in your own HTTP retry loops without idempotency. Retry and backoff policy for plain HTTP calls is a networking topic.
- A callable times out on the client with deadline-exceeded. Did the function stop running?Not necessarily. The timeout in `HttpsCallableOptions`, 60 seconds by default, is enforced by the client, and the server may still complete the work. For state-changing calls, such as redeeming a coupon, make the function idempotent so a retry is safe.
- The function throws an unexpected TypeError. What does the Flutter client receive?A `FirebaseFunctionsException` with code `internal` and a generic message. Only errors the function deliberately throws as callable errors carry a specific code, message and `details`. That keeps server internals from leaking to clients, so log the real error on the server.
saying these in an interview costs you the question
- You must add the user's ID token to the callable request yourself.
- Callable errors arrive as a generic PlatformException you parse by message.
- deadline-exceeded proves the function stopped executing on the server.
- Any Dart object can be passed to call() and is serialised automatically.
- A thrown exception's details always reach the client unchanged.