Connect FlutterFlow to TensorFlow two ways: (1) on-device inference — add the tflite_flutter pub.dev package as a Custom Action, bundle a .tflite model, and run predictions offline on iOS and Android; or (2) server inference — call a self-hosted TensorFlow Serving REST endpoint via a FlutterFlow API Group. On-device is the unique path most competitor pages miss. Both approaches keep secret keys off the client.
| Fact | Value |
|---|---|
| Tool | TensorFlow |
| Category | AI & ML |
| Method | Custom Action (Dart) |
| Difficulty | Advanced |
| Time required | 60 minutes |
| Last updated | July 2026 |
Two TensorFlow Paths in FlutterFlow: On-Device vs Server Inference
TensorFlow is the most widely used ML framework in the world, and FlutterFlow's Flutter foundation gives you a capability most no-code tools don't have: the ability to run ML models directly on the user's device, offline, with no network call required. This is the TensorFlow Lite (TFLite) path, and it's the reason founders building fitness apps with pose detection, healthcare apps with symptom classification, or retail apps with image recognition should consider FlutterFlow specifically.
The on-device path uses the tflite_flutter pub.dev package in a FlutterFlow Custom Action. You bundle a pre-trained or fine-tuned .tflite model as an asset in your app (converted from a standard TensorFlow SavedModel using the TFLite Converter). The Custom Action loads the interpreter at runtime, feeds it input tensors (e.g. a camera frame resized to 224×224 pixels, normalized to [-1, 1]), and reads the output predictions array. Everything happens on device — no API key, no internet connection needed, no per-call cost. The tradeoffs: larger models inflate your APK size, and very large models (>50MB) should be downloaded at runtime rather than bundled; also, this path does not work in FlutterFlow web preview — you must test on a real device or APK.
The server inference path uses a TensorFlow Serving instance — an open-source Docker-based serving system you host yourself (on GCP, AWS, or any server). TF Serving exposes a REST API at `http://your-host:8501/v1/models/{model_name}:predict` that accepts a JSON body with an `instances` array. In FlutterFlow this is a standard API Group POST call. Because TF Serving endpoints are typically on a private network, you front them with a Cloud Function proxy so FlutterFlow can reach them without exposing them publicly — the proxy also resolves CORS for web builds.
Integration method
There are two distinct integration paths. For on-device inference — the path most builders actually want for mobile ML features like image classification, pose detection, or text classification — you write a FlutterFlow Custom Action in Dart that uses the tflite_flutter pub.dev package to load a bundled .tflite model and run interpreter.run() on input data. For server-based inference, you build a FlutterFlow API Group that POSTs input data to a TensorFlow Serving REST endpoint (your-host:8501/v1/models/{model}:predict) and parses the predictions array — if the endpoint is internal or secured, a Cloud Function proxy sits in between. The on-device path is unique to Flutter/FlutterFlow and does not exist in web-only AI builders.
Prerequisites
- A FlutterFlow project on a paid plan (Custom Actions require a paid tier)
- A pre-trained .tflite model file — convert from SavedModel using TFLite Converter, or download a prebuilt model from TensorFlow Hub (tfhub.dev)
- Knowledge of your model's input tensor shape and data type (e.g. [1, 224, 224, 3] float32 for image models) — check the model's documentation or use Netron to inspect the .tflite file
- For TF Serving path: a running TensorFlow Serving Docker container with the REST API port exposed, or a Cloud Function proxy fronting an internal TF Serving instance
- FlutterFlow Basic plan or above for GitHub integration (needed to export and add .tflite model assets)
Step-by-step guide
Choose your path and prepare the model
Before opening FlutterFlow, decide which integration path fits your use case: **On-device (TFLite):** Best for image/vision tasks (classification, detection, pose), text classification, and any feature that benefits from offline operation or ultra-low latency. Works on iOS and Android native builds. Will NOT work in FlutterFlow web preview (throws MissingPluginException or UnimplementedError) — plan to test on a real device. **Server inference (TF Serving):** Best for large models that won't fit in an app bundle, models you retrain frequently, or complex tabular/structured-data models hosted on your infrastructure. For the on-device path, prepare your .tflite file. If you have a TensorFlow SavedModel, convert it: - Use TensorFlow's Python converter: `tf.lite.TFLiteConverter.from_saved_model(path).convert()` - Or download a ready-to-use model from TensorFlow Hub (tfhub.dev) — MobileNet, EfficientDet, PoseNet are all available as .tflite Inspect the model with Netron (netron.app) to confirm: input tensor name, shape (e.g. [1, 224, 224, 3]), and dtype (float32 or uint8). You'll need these exact values in your Dart code — a shape mismatch is the #1 cause of runtime failures. For TF Serving: confirm your server is running (`docker run -p 8501:8501 tensorflow/serving --model_name=mymodel --model_base_path=/models/mymodel`) and the endpoint responds to `curl http://localhost:8501/v1/models/mymodel` before building in FlutterFlow.
Pro tip: If your model is larger than 25MB, don't bundle it in the app — download it at runtime from Firebase Storage or a CDN and cache it on device. Use tflite_flutter's Interpreter.fromFile() once downloaded, or Interpreter.fromAsset() for bundled models.
Expected result: You have a .tflite file ready (and know its input shape), or a running TF Serving instance with its :predict endpoint URL confirmed. You've chosen your integration path.
Add tflite_flutter and the model asset to FlutterFlow
In your FlutterFlow project, go to Custom Code in the left navigation panel, then click Dependencies (or Pubspec Dependencies). Click + Add and type `tflite_flutter`. FlutterFlow will look up the package on pub.dev — select the latest stable version. This single package handles model loading and inference for both iOS and Android. Next, you need to add your .tflite model as a Flutter asset. FlutterFlow supports adding asset files through the GitHub integration (Basic plan or above required). Push your .tflite file to your connected GitHub repo under `assets/models/your_model.tflite`. In your pubspec.yaml (accessible through the GitHub repo), add the asset path under `flutter: assets:`. If you don't have GitHub integration set up, use FlutterFlow's built-in Media Assets feature: go to the Media panel (image icon in the left nav), upload your .tflite file. Note the generated asset URL — you'll reference it in the Custom Action code as an asset path. For platform permissions: TFLite itself has no special permissions. If your app uses the camera to capture frames for inference, add camera permission in FlutterFlow's Permissions settings (Settings & Integrations → Permissions → Camera). The Info.plist description and AndroidManifest entries are handled automatically by FlutterFlow's permission system.
Pro tip: After adding tflite_flutter as a dependency, FlutterFlow shows a banner saying custom code changes require a re-compile. Use Run Mode (the play button) in a local test build to verify the dependency resolves correctly before writing the Custom Action code.
Expected result: The tflite_flutter package appears in FlutterFlow's Dependencies list. Your .tflite model is accessible as an asset in the project. The project compiles without errors.
Write the TFLite inference Custom Action
Now create the Custom Action that runs inference. In FlutterFlow, go to Custom Code → + Add → Action. Name it `runTFLiteInference`. Add the following arguments: - `imageBytes` (Bytes) — the raw image data to classify - Return type: String (or a JSON string with all results) Here's the complete Dart Custom Action. It loads the model from assets, preprocesses a 224×224 image to float32, runs inference, and returns the top predicted label index: ```dart import 'package:tflite_flutter/tflite_flutter.dart'; import 'dart:typed_data'; import 'dart:io'; Future<String> runTFLiteInference(Uint8List imageBytes) async { // Load model from assets (or from a file path if downloaded at runtime) final interpreter = await Interpreter.fromAsset('assets/models/your_model.tflite'); // Prepare input: resize and normalize to float32 [1, 224, 224, 3] // This assumes imageBytes is already a raw 224x224 RGB bitmap // For camera frames, use the image package to resize first final inputShape = interpreter.getInputTensor(0).shape; // [1, 224, 224, 3] final outputShape = interpreter.getOutputTensor(0).shape; // e.g. [1, 1001] for ImageNet var input = Float32List(1 * 224 * 224 * 3); for (int i = 0; i < imageBytes.length && i < input.length; i++) { // Normalize pixel values from [0, 255] to [-1.0, 1.0] input[i] = (imageBytes[i] / 127.5) - 1.0; } var inputTensor = input.reshape([1, 224, 224, 3]); // Prepare output buffer var output = List.filled(1001, 0.0).reshape([1, 1001]); // Run inference interpreter.run(inputTensor, output); interpreter.close(); // Find top prediction final outputList = (output as List)[0] as List<double>; int topIdx = 0; double topConf = 0.0; for (int i = 0; i < outputList.length; i++) { if (outputList[i] > topConf) { topConf = outputList[i]; topIdx = i; } } return '$topIdx:${(topConf * 100).toStringAsFixed(1)}%'; } ``` Paste this into the Custom Action code editor. Adjust the model filename, input shape, and normalization formula to match your specific model. For uint8 quantized models, use `Uint8List` input without normalization instead of Float32List.
1import 'package:tflite_flutter/tflite_flutter.dart';2import 'dart:typed_data';34Future<String> runTFLiteInference(Uint8List imageBytes) async {5 final interpreter = await Interpreter.fromAsset('assets/models/your_model.tflite');67 // Normalize to float32 [-1.0, 1.0] for MobileNet-style models8 var input = Float32List(1 * 224 * 224 * 3);9 for (int i = 0; i < imageBytes.length && i < input.length; i++) {10 input[i] = (imageBytes[i] / 127.5) - 1.0;11 }12 var inputTensor = input.reshape([1, 224, 224, 3]);1314 // Output shape: [1, 1001] for MobileNet ImageNet (1000 classes + background)15 var output = List.filled(1001, 0.0).reshape([1, 1001]);1617 interpreter.run(inputTensor, output);18 interpreter.close();1920 final outputList = (output as List)[0] as List<double>;21 int topIdx = 0;22 double topConf = 0.0;23 for (int i = 0; i < outputList.length; i++) {24 if (outputList[i] > topConf) {25 topConf = outputList[i];26 topIdx = i;27 }28 }29 return '$topIdx:${(topConf * 100).toStringAsFixed(1)}%';30}Pro tip: Always call interpreter.close() after inference to release native memory. Leaving interpreters open in a long-running session causes memory leaks on mobile devices.
Expected result: The Custom Action appears in FlutterFlow's Custom Code panel without compilation errors. You can add it to a widget's Action Flow in the next step.
Wire the Custom Action to a camera capture button
With the Custom Action ready, build the UI that triggers inference. Add a Camera or Image Picker widget to your screen, or use FlutterFlow's built-in Camera Action (available via Action Flow Editor → Camera → Capture Photo). The camera action returns image bytes that you'll pass to the Custom Action. Add a Button widget labelled 'Analyse' (or trigger automatically on photo capture). In the Action Flow Editor for the button or photo-capture callback: 1. Add action: Camera → Capture Photo → store result in an App State variable `capturedImageBytes` (type: Bytes) 2. Add action: Custom → runTFLiteInference → pass `capturedImageBytes` → store result in `predictionResult` (String App State) 3. Add action: Update UI — bind a Text widget to `predictionResult` to display the label and confidence Important: Custom Actions using native packages (like tflite_flutter) do not execute in FlutterFlow's web-based Run Mode or Test Mode. You must build an APK (Android) or use the FlutterFlow iOS companion app to test on a real device. To build an APK, click the Build icon in the top-right of FlutterFlow → Build for Android → Download APK, then install it on a physical device. Add a platform guard so the inference action is only triggered on iOS or Android — if a web user somehow triggers it, show a friendly message rather than a crash: ```dart if (!Platform.isAndroid && !Platform.isIOS) { return 'On-device inference is only available on iOS and Android.'; } ``` For a more polished experience, show a CircularProgressIndicator while inference is running and clear it once `predictionResult` is populated.
Pro tip: Inference on the first call includes model loading time (can be 200ms–1s depending on model size). For real-time video processing, load the interpreter once when the page loads (in an On Page Load action) and keep it in an App State variable — don't reload per frame.
Expected result: Tapping the Analyse button captures a photo, runs it through the TFLite model, and displays the prediction label and confidence on screen. This works on a real iOS or Android device.
Alternative path: TF Serving API Group for server inference
If you chose the server inference path instead of on-device, this step replaces Steps 2–4. Your TensorFlow Serving instance exposes a REST API at `http://your-host:8501/v1/models/{model_name}:predict`. Because this endpoint is on your infrastructure (not a public service), you route calls through a Cloud Function proxy that keeps the endpoint private and resolves CORS for web builds. Deploy a minimal Cloud Function proxy: ```javascript const functions = require('firebase-functions'); const axios = require('axios'); exports.tfServingProxy = functions.https.onRequest(async (req, res) => { res.set('Access-Control-Allow-Origin', '*'); if (req.method === 'OPTIONS') { res.set('Access-Control-Allow-Methods', 'POST'); res.set('Access-Control-Allow-Headers', 'Content-Type'); return res.status(204).send(''); } try { const response = await axios.post( `${process.env.TF_SERVING_URL}/v1/models/${req.query.model}:predict`, req.body ); return res.json(response.data); } catch (err) { return res.status(err.response?.status || 500).json({ error: err.message }); } }); ``` Set TF_SERVING_URL as an environment variable (e.g. `http://10.128.0.5:8501` for an internal GCP VM). In FlutterFlow, create an API Group 'TensorFlowServing' with the Base URL set to your Cloud Function URL. Add an API Call 'predict' — POST, endpoint `/tfServingProxy?model=your_model_name`. Body: `{"instances": [{{ inputData }}]}` with `inputData` as a JSON variable. In Response & Test, paste a sample Serving response: `{"predictions": [[0.02, 0.97, 0.01]]}`. Add JSON paths: `$.predictions[0]` → `predictionScores`. Bind the result to your UI. For the input: if your model takes structured data (e.g. transaction features), pass a JSON array directly. If it takes images, you'll need to either send base64-encoded pixel data or have your server accept a different input format — check your model's serving signature with `saved_model_cli show --all`.
1const functions = require('firebase-functions');2const axios = require('axios');34exports.tfServingProxy = functions.https.onRequest(async (req, res) => {5 res.set('Access-Control-Allow-Origin', '*');6 if (req.method === 'OPTIONS') {7 res.set('Access-Control-Allow-Methods', 'POST');8 res.set('Access-Control-Allow-Headers', 'Content-Type');9 return res.status(204).send('');10 }11 try {12 const response = await axios.post(13 `${process.env.TF_SERVING_URL}/v1/models/${req.query.model}:predict`,14 req.body,15 { headers: { 'Content-Type': 'application/json' } }16 );17 return res.json(response.data);18 } catch (err) {19 return res.status(err.response?.status || 500).json({ error: err.message });20 }21});Pro tip: Use TF Serving's gRPC interface (port 8500) instead of REST for high-throughput production workloads — it's significantly faster. For FlutterFlow, the REST path via proxy is sufficient for most mobile app call volumes.
Expected result: For TF Serving path: the FlutterFlow API Call successfully returns predictions from your TF Serving instance via the Cloud Function proxy. Predictions display correctly in the bound UI widgets.
Common use cases
Real-time image classifier for a plant identification app
A FlutterFlow app that uses the device camera to capture a photo, runs it through a bundled MobileNet .tflite model in a Custom Action, and displays the top-3 plant species predictions with confidence scores — all offline, with no API key or internet connection required during classification.
Build a FlutterFlow screen with a camera capture button that takes a photo, runs it through an on-device TFLite image classification model, and shows a list of the top 3 predicted labels with percentage confidence scores.
Copy this prompt to try it in FlutterFlow
Fitness pose detection with real-time feedback
A workout coaching app that uses the device camera and a bundled PoseNet or MoveNet .tflite model in a Custom Action to detect body keypoints in real time, evaluate squat or pushup form, and show a live overlay with pose corrections — no network latency, works in airplane mode during gym sessions.
Build a FlutterFlow workout screen that runs a MoveNet pose estimation model on camera frames and shows colored dots on detected body keypoints, with a status label that says 'Good form' or 'Adjust your back' based on keypoint angles.
Copy this prompt to try it in FlutterFlow
Server-based fraud or anomaly detection for a fintech app
A FlutterFlow fintech app that sends transaction feature vectors to a self-hosted TensorFlow Serving endpoint via a Cloud Function proxy, receives a fraud probability score, and displays a risk badge on each transaction in a list — combining the power of a large server-hosted model with a mobile-first UI.
Build a FlutterFlow transaction list screen that calls a Cloud Function proxy to send a transaction's amount, category, and location to TensorFlow Serving, receives a fraud score between 0 and 1, and shows a red badge on transactions scoring above 0.7.
Copy this prompt to try it in FlutterFlow
Troubleshooting
MissingPluginException or UnimplementedError when running the Custom Action
Cause: tflite_flutter uses native code (C++ TFLite runtime) that is not available in FlutterFlow's web-based Test Mode or Run Mode. These exceptions appear when the Custom Action is triggered in a preview environment rather than on a real device.
Solution: Build an APK (Build → Build for Android → Download APK in FlutterFlow) and install it on a physical Android device, or use FlutterFlow's iOS companion app on a real iPhone. Never test TFLite Custom Actions in the browser-based Run Mode — the plugin does not load in that environment. Add a Platform.isAndroid || Platform.isIOS guard in your Custom Action to return a fallback value on unsupported platforms.
interpreter.run() throws an error or returns garbage predictions
Cause: The input tensor shape or dtype does not match what the model expects. This is the most common TFLite failure: passing [224, 224, 3] when the model expects [1, 224, 224, 3] (missing batch dimension), or passing normalized float32 values to a uint8 quantized model.
Solution: Open your .tflite model in Netron (netron.app) and check the input tensor's exact shape and dtype. Update your Dart code to match exactly. For float32 models, normalize pixels to [-1.0, 1.0] (MobileNet style) or [0, 1.0] (EfficientDet style) depending on the model's training preprocessing. For uint8 quantized models, pass raw 0–255 byte values with no normalization. Use interpreter.getInputTensor(0).shape and .type in your Dart code to log the expected shape at runtime for debugging.
TF Serving proxy returns 404 Not Found
Cause: The model name in the :predict URL does not match the directory name your TF Serving instance was started with, or the model version subdirectory is missing. TF Serving requires the model to be in a directory named with an integer version number (e.g. /models/mymodel/1/).
Solution: Confirm the exact model name by calling `http://your-tf-serving-host:8501/v1/models/your_model_name` — it should return a JSON status object. If it 404s, check your Docker container's --model_name flag and the directory structure: the path must be /models/{model_name}/{version_number}/saved_model.pb. Update the model name in the Cloud Function environment variable and redeploy.
App bundle size increased dramatically after adding the .tflite model
Cause: Large .tflite model files (e.g. ResNet50 at ~90MB) bundled as app assets inflate the APK size, potentially exceeding app store limits and causing slow downloads for users.
Solution: For models larger than 25MB, don't bundle them — download at runtime from Firebase Storage or a CDN on first launch and cache in the app's document directory. Use Interpreter.fromFile(file.path) instead of Interpreter.fromAsset('...') once the file is downloaded. Consider a smaller, quantized model variant — TFLite quantized models are typically 4x smaller than full float32 models with minimal accuracy loss.
Best practices
- Always inspect your .tflite model in Netron (netron.app) before writing Dart code — knowing the exact input shape and dtype prevents the most common inference failures
- Call interpreter.close() after every inference call to release native memory and prevent leaks
- For real-time video inference, load the interpreter once on page load and reuse it rather than loading it per-frame
- Bundle only small models (under 25MB) in the app; download larger models at runtime from Firebase Storage and cache locally
- Use quantized uint8 TFLite models for production mobile apps — they are 4x smaller and nearly as accurate as float32 versions
- Always add a Platform.isAndroid || Platform.isIOS guard in Custom Actions using tflite_flutter to avoid crashes on web builds
- Keep TF Serving endpoints private behind a Cloud Function proxy — do not expose the serving port directly to the internet
- Test TFLite Custom Actions on a physical device (APK or companion app), never in FlutterFlow's web-based Test Mode
Alternatives
If you need fully managed model hosting without running your own TF Serving server, Vertex AI (Google Cloud AI Platform) provides a managed endpoint with auto-scaling — the trade-off is per-prediction billing and Google service-account auth via a Cloud Function proxy.
For natural language tasks (chat, summarization, classification) where a pre-trained general model is sufficient, OpenAI's Chat Completions API via a Cloud Function proxy is faster to integrate than training and deploying a custom TensorFlow model.
DeepAI provides single-purpose hosted AI endpoints (image generation, text summarization) with a simpler API-key-in-proxy setup — a good alternative when you don't want to host your own TF Serving infrastructure.
Frequently asked questions
Do I need to train my own TensorFlow model or can I use a pre-trained one?
You can absolutely use pre-trained models. TensorFlow Hub (tfhub.dev) has hundreds of .tflite models ready to download for image classification, object detection, pose estimation, text embedding, and more. For most app features, a pre-trained MobileNet or EfficientDet model works well with no training required. You'd only train a custom model if your classification task is very domain-specific (e.g. detecting defects in a specific industrial part) and no public model covers it.
Can TFLite models run on both iOS and Android from the same Custom Action?
Yes. The tflite_flutter package supports both iOS and Android with the same Dart API. The .tflite model format is cross-platform — one model file works on both operating systems. The main difference is platform permission setup: camera access requires NSCameraUsageDescription in Info.plist (iOS) and android.permission.CAMERA in AndroidManifest.xml (Android), which FlutterFlow's Permissions settings handle automatically.
Why won't the Custom Action work in FlutterFlow's Run Mode?
FlutterFlow's Run Mode (the play button preview) renders your app in a web browser using Flutter's web renderer. The tflite_flutter package uses the TFLite C++ runtime through Dart FFI (Foreign Function Interface), which is not available in a browser context. This is a platform limitation, not a bug. Always test TFLite Custom Actions using the APK download or FlutterFlow's iOS build — they work correctly on real devices.
What is TensorFlow Serving and when do I need it instead of TFLite?
TensorFlow Serving is a server-based model serving system you run yourself (typically via Docker). Use it when: your model is too large to bundle in a mobile app, you need to update the model without resubmitting the app to stores, or you need to run inference on server-grade hardware (GPU). TFLite is the right choice for offline, low-latency, privacy-preserving use cases where the model can fit in the app bundle.
How do I update the .tflite model in my app without a full app store submission?
Upload the new model to Firebase Storage. In your Custom Action, check for a newer model version on app launch, download it to the device's document directory if available, and use Interpreter.fromFile(path) with the downloaded file instead of Interpreter.fromAsset(). This way, model updates are pushed through Firebase Storage without requiring users to update the app from the store. Store the model version number in Firestore to track which version each device is using.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation