Connect Bubble to TensorFlow Serving — the production HTTP inference server that wraps your trained TensorFlow model in a REST endpoint. TensorFlow itself is a training library; Bubble connects to its Serving component deployed on a cloud VM behind a public HTTPS URL. POST your input data as an instances array and receive predictions in response, with no auth required unless you add a reverse proxy layer.
| Fact | Value |
|---|---|
| Tool | TensorFlow |
| Category | AI & ML |
| Method | Bubble API Connector |
| Difficulty | Advanced |
| Time required | 3–6 hours (includes infrastructure setup) |
| Last updated | July 2026 |
TensorFlow Serving — self-hosted ML inference in Bubble
There is an important distinction that many Bubble builders miss when searching for 'TensorFlow integration': **TensorFlow** is a Python library for training ML models on your local machine or a cloud VM. It does not expose an HTTP API by itself. **TensorFlow Serving** is the companion production server that takes a trained SavedModel and serves predictions via HTTP REST and gRPC.
Bubble's API Connector cannot run Python or load TensorFlow models directly. What it CAN do is call TensorFlow Serving's REST API endpoint — an HTTPS URL that accepts JSON input and returns JSON predictions. Your architecture is:
1. You train a TensorFlow model (on your machine, a GCP VM, or a Colab notebook) 2. You export the model as a SavedModel 3. You run TensorFlow Serving (Docker container or apt package) on a cloud VM, passing it your SavedModel 4. TensorFlow Serving exposes port 8501 with a REST API 5. You add HTTPS (nginx with Let's Encrypt or a cloud load balancer) because Bubble requires HTTPS 6. Bubble's API Connector POSTs to your HTTPS prediction URL
The main Bubble use case is non-technical founders who have a trained ML model (perhaps built by a data science team) and want to surface its predictions in a Bubble app for business users — without needing a full custom backend development project. A Bubble form collects the prediction inputs, the API call fires, and the result displays in the UI.
The input schema is the most common stumbling block: the field names and types inside the instances array must exactly match the model's SignatureDef — defined when the model was exported, not when TensorFlow Serving is configured.
Integration method
Bubble API Connector pointing at a self-hosted TensorFlow Serving REST endpoint over public HTTPS — Bubble requires HTTPS, so a reverse proxy (nginx with TLS) or a cloud load balancer with SSL termination must sit in front of TensorFlow Serving's port 8501.
Prerequisites
- A trained TensorFlow SavedModel exported and ready to serve (model directory with saved_model.pb and variables/ folder)
- A cloud VM instance (GCP Compute Engine, AWS EC2, DigitalOcean Droplet, or Render) with TensorFlow Serving installed via Docker or apt package
- An HTTPS endpoint for your TensorFlow Serving instance — either a cloud load balancer with SSL or an nginx reverse proxy with a Let's Encrypt certificate
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → API Connector by Bubble → Install)
- Knowledge of your model's input tensor names and data types (from the model's SignatureDef — inspect using `saved_model_cli show --dir /model --all`)
Step-by-step guide
Understand TensorFlow Serving and its REST API format
Before configuring Bubble, make sure your TensorFlow Serving instance is running and accessible. This step clarifies the REST API structure you will be calling from Bubble. **TensorFlow Serving REST API format:** POST `/v1/models/{model_name}:predict` The `model_name` matches the directory name you mounted when starting TensorFlow Serving. For example, if you started Serving with `--model_name=price_predictor`, the endpoint is: `http://your-server:8501/v1/models/price_predictor:predict` The request body always wraps inputs in an `instances` array: ```json { "instances": [ { "feature_1": value1, "feature_2": value2 } ] } ``` The response always wraps outputs in a `predictions` array: ```json { "predictions": [ [...] ] } ``` For tabular models, each item in `predictions` is typically an array of probabilities (one per class) or a single float (for regression). For classification, `predictions[0]` is often an array — `predictions[0][class_index]` gives the probability for a specific class. To inspect your model's expected input field names, run: `saved_model_cli show --dir /path/to/saved_model --all` This outputs the SignatureDef with exact tensor names (e.g., `serving_default_input_1`, `dense_input`) — these are the field names you must use inside the instances object. Wrong names return 400 errors without clear error messages. **Version handling:** If your serving instance has multiple model versions, the endpoint includes the version: `/v1/models/{model_name}/versions/{version}:predict` Omitting the version serves the latest version automatically.
1// TensorFlow Serving REST API:2// Base: http://your-host:85013// Predict endpoint: POST /v1/models/{model_name}:predict4//5// Request body:6{7 "instances": [8 {9 "your_input_tensor_name": value10 }11 ]12}1314// Response body:15{16 "predictions": [17 [0.1, 0.7, 0.2] // array of class probabilities for classification18 ]19}2021// For regression models:22{23 "predictions": [24 [42.5] // array containing single predicted value25 ]26}2728// Inspect model inputs:29// saved_model_cli show --dir /path/to/savedmodel --all30// Look for: signature_def['serving_default'].inputs31// Note the exact tensor name strings — use these in instances3233// Health check endpoint:34// GET /v1/models/{model_name}35// Returns model status and version infoPro tip: Run the TensorFlow Serving health check endpoint (`GET /v1/models/{model_name}`) from a browser or curl to verify the server is accessible and the model is loaded before configuring Bubble. If the health check returns 'Model status: AVAILABLE', the serving instance is ready to receive predictions.
Expected result: You understand the TensorFlow Serving REST API structure, have confirmed your serving instance is running, and have noted the exact input tensor names from your model's SignatureDef.
Add HTTPS to your TensorFlow Serving endpoint
Bubble's API Connector requires HTTPS for all external API calls. TensorFlow Serving runs on plain HTTP at port 8501 by default — you must add TLS termination before Bubble can connect to it. **Option A: nginx reverse proxy with Let's Encrypt (recommended for cloud VMs)** Install nginx on your server: ``` sudo apt install nginx certbot python3-certbot-nginx ``` Create an nginx server block that proxies HTTPS traffic to TensorFlow Serving's local HTTP port 8501. After configuring nginx, run Certbot to obtain a free Let's Encrypt certificate: ``` sudo certbot --nginx -d your-domain.com ``` Certbot automatically updates the nginx config to enable HTTPS with the Let's Encrypt certificate. **Option B: Cloud load balancer (GCP, AWS, DigitalOcean)** Create a cloud load balancer with an SSL certificate pointing to your TensorFlow Serving VM on port 8501. The load balancer handles TLS termination — Bubble sends HTTPS to the load balancer, which forwards plain HTTP to port 8501 internally. This is the managed alternative that requires no nginx configuration. **Option C: Run on GCP AI Platform or similar (managed)** If you deployed your model via Google Cloud AI Platform (Vertex AI), it already serves via HTTPS with Google-managed TLS. See the Vertex AI guide for the authentication steps required in that case. **Adding a simple API key check in nginx (optional but recommended):** Add a `valid_referers` or `if ($http_x_api_key != 'your_secret')` check in your nginx location block to reject unauthenticated requests. Store this key as a Private header in Bubble's API Connector. This is simpler than setting up proper service account auth and sufficient for protecting a self-hosted model endpoint.
1# nginx reverse proxy configuration for TensorFlow Serving2# File: /etc/nginx/sites-available/tensorflow-serving34server {5 listen 443 ssl;6 server_name your-domain.com;78 ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;9 ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;1011 location /v1/models/ {12 # Optional: simple API key check13 if ($http_x_api_key != 'your-random-secret-key') {14 return 401;15 }1617 proxy_pass http://127.0.0.1:8501;18 proxy_set_header Host $host;19 proxy_set_header X-Real-IP $remote_addr;20 proxy_read_timeout 60s;21 }22}2324server {25 listen 80;26 server_name your-domain.com;27 return 301 https://$server_name$request_uri;28}2930# Start TensorFlow Serving (Docker example):31# docker run -p 8501:8501 \32# --mount type=bind,source=/path/to/savedmodel,target=/models/price_predictor \33# -e MODEL_NAME=price_predictor \34# tensorflow/servingPro tip: After setting up nginx, test the HTTPS endpoint with curl before configuring Bubble: `curl -X POST https://your-domain.com/v1/models/price_predictor:predict -H 'Content-Type: application/json' -H 'X-Api-Key: your-secret' -d '{"instances": [{"feature": 1.0}]}'`. A valid prediction response confirms the full HTTPS + nginx + TensorFlow Serving stack is working before involving Bubble.
Expected result: Your TensorFlow Serving endpoint is accessible via HTTPS at a public URL. A curl test from your local machine returns a valid prediction response. The nginx config optionally rejects requests without the correct API key header.
Configure Bubble's API Connector for TensorFlow Serving
In your Bubble editor, click Plugins in the left sidebar and locate the API Connector plugin. Click 'Add another API' and name it 'TensorFlow Serving.' Set the base URL to your TensorFlow Serving HTTPS endpoint, stopping before the model path: `https://your-domain.com` If you added an API key check in nginx, add a shared header at the API group level: - Name: `X-Api-Key` - Value: `your-random-secret-key` - Check the **Private** checkbox Add another shared header: - Name: `Content-Type` - Value: `application/json` Click 'Add another call' and name it 'Predict.' Set: - Method: POST - URL: `/v1/models/price_predictor:predict` (replace `price_predictor` with your model name) - Use as: Action For the JSON body, create the instances array. The structure depends on your model's input schema. For a tabular model with features `feature_1`, `feature_2`, and `feature_3`: ```json { "instances": [ { "feature_1": "<dynamic_feature_1>", "feature_2": "<dynamic_feature_2>", "feature_3": "<dynamic_feature_3>" } ] } ``` Mark each feature as a dynamic parameter with the appropriate type (text, number, or list depending on your model's input). Click 'Initialize call.' Provide real test values that match your model's expected input types (numbers in the correct range, strings in the correct format). A successful initialization returns the `predictions` array — Bubble detects the response structure for binding.
1// API Connector: TensorFlow Serving → Predict2// Method: POST3// Base URL: https://your-domain.com4// Call URL: /v1/models/your_model_name:predict5//6// Shared headers:7// X-Api-Key: your-secret // mark Private (if nginx auth enabled)8// Content-Type: application/json9//10// JSON body (tabular model example):11{12 "instances": [13 {14 "usage_frequency": "<dynamic_usage_frequency>",15 "days_since_login": "<dynamic_days_since_login>",16 "support_tickets": "<dynamic_support_tickets>",17 "subscription_age": "<dynamic_subscription_age>"18 }19 ]20}2122// Note: Field names must EXACTLY match the model's SignatureDef input names23// Use saved_model_cli to inspect: saved_model_cli show --dir /model --all2425// Response:26{27 "predictions": [28 [0.23, 0.77] // Example: [probability_no_churn, probability_churn]29 ]30}Pro tip: During the Initialize call, enter values that reflect real model inputs — if your model expects normalized features (e.g., values between 0 and 1), provide values in that range. Values outside the expected range may return valid JSON but with nonsensical predictions that look like successful calls but aren't. Verify predictions against known outputs from your training evaluation before wiring to the Bubble UI.
Expected result: The API Connector call initializes successfully. Bubble detects the `predictions` array in the response. The prediction result fields are available in the workflow editor for binding to Bubble elements.
Build a Bubble workflow that sends prediction requests
With the API Connector configured, build the Bubble workflow that collects user inputs and fires the prediction call. On a Bubble page, create a form with input fields matching your model's expected inputs. For a churn prediction model: - Number Input: 'Days since last login' - Number Input: 'Weekly usage count' - Number Input: 'Support tickets (last 30 days)' - Number Input: 'Subscription age (months)' - Button: 'Predict churn risk' - Text element: 'Prediction result' (initially hidden) In the workflow editor, create a workflow triggered by 'Button is clicked.' **Action 1:** Show a loading indicator. **Action 2:** Call 'Plugins → TensorFlow Serving → Predict.' Map the body parameters to the form inputs: - `usage_frequency` = Weekly usage input's value - `days_since_login` = Days input's value - `support_tickets` = Tickets input's value - `subscription_age` = Subscription age input's value **Action 3:** Set a Custom State to the result. The prediction value path is `predictions[0]` (first item in the predictions array). For multi-class classification, this is itself an array of probabilities — use `predictions[0][1]` to get the probability for the positive class (e.g., churn=yes). **Action 4:** Display the result. Show the result Text element and set its content to the Custom State. For binary classification, map the probability to a human-readable label: - If Custom State > 0.7: show 'High risk' in red - If Custom State > 0.4: show 'Medium risk' in yellow - Otherwise: show 'Low risk' in green Use Bubble's Conditional feature on the Text element's color and content based on the Custom State value. For security and reliability, run the API call in a Backend Workflow rather than client-side — this ensures the API key header is never visible in browser DevTools even if something goes wrong with the Private checkbox during Bubble updates.
1// Bubble workflow: Predict button clicked2//3// Action 1: Show 'Loading' element4//5// Action 2: Plugins → TensorFlow Serving → Predict6// usage_frequency: Usage Input's value7// days_since_login: Days Input's value8// support_tickets: Tickets Input's value9// subscription_age: Age Input's value10//11// Action 3: Set Custom State 'churn_probability'12// Value: Result of step 2's predictions[0][1]13// (index 1 = probability of churn=yes for binary classifier)14//15// Action 4: Hide 'Loading' element16// Action 5: Show 'Result' element1718// Bubble conditional for risk badge color:19// Text element 'Risk Level':20// Default text: 'Low Risk' (green)21// When churn_probability state > 0.4: 'Medium Risk' (yellow)22// When churn_probability state > 0.7: 'High Risk' (red)2324// Parsing predictions for different model types:25//26// Binary classification (2 classes):27// predictions[0] = [0.23, 0.77] → predictions[0][1] = churn probability28//29// Multi-class (N classes):30// predictions[0] = [0.1, 0.7, 0.2] → argmax = class 1 (highest probability)31//32// Regression (single output):33// predictions[0] = [42.5] → predictions[0][0] = predicted valuePro tip: When mapping prediction output back to business labels, create a Bubble Option Set with your class labels and their corresponding numeric indices. This lets you display 'Category: Electronics' instead of 'Class index: 3' — and makes it easy to update labels if your model is retrained with new classes.
Expected result: Clicking the Predict button sends form data to TensorFlow Serving, receives a prediction in return, and displays a human-readable result in the Bubble UI. The loading indicator shows and hides correctly around the API call.
Handle image model inputs with base64 encoding
If your TensorFlow model takes images as input (computer vision models for classification, object detection, or segmentation), the integration requires one additional step: converting the user's uploaded image to a base64 string before sending it to TensorFlow Serving. TensorFlow image models typically expect one of these formats in the instances array: - Base64-encoded image bytes: `{ "b64": "<base64_string>" }` - A flat pixel array (uncommon for Bubble integrations due to size) In Bubble, users upload images via the File Uploader element. This generates a URL pointing to Bubble's file storage. To convert this URL to base64, you need a Backend Workflow that fetches the file and encodes it — or you can pass the image URL directly to a separate endpoint that handles the conversion. **Practical approach for Bubble:** Use a simple Cloudflare Worker or lightweight Cloud Function that: 1. Accepts an image URL as a parameter 2. Downloads the image 3. Converts it to base64 4. Returns the base64 string Alternatively, if your TensorFlow model accepts URLs directly (some custom models do), pass the Bubble file URL as a feature string — this requires modifying the TensorFlow Serving model's preprocessing layer. **For base64 models, the instances body looks like:** ```json { "instances": [ { "b64": "<base64_encoded_image_string>" } ] } ``` The base64 string can be quite large (hundreds of KB for typical photos) — this consumes significant Bubble Workload Units and may hit API Connector body size limits. For image models, consider serving the TensorFlow endpoint via Google Cloud AI Platform (Vertex AI) which handles large payloads more gracefully and provides managed scaling.
1// TensorFlow Serving body format for image models:2{3 "instances": [4 {5 "b64": "<base64_encoded_image_string>"6 }7 ]8}910// Note: The "b64" key is TensorFlow Serving's standard way to pass11// binary data encoded as base64 in a JSON body.1213// Bubble workflow for image prediction:14// 1. User uploads file via File Uploader15// 2. Backend Workflow: call base64 conversion endpoint16// (a Cloud Function or Cloudflare Worker that fetches and encodes the file)17// 3. Store base64 string in Custom State18// 4. Call TensorFlow Serving Predict with {"b64": Custom State value}19// 5. Parse predictions array and display result2021// Image input limitations:22// - base64 of a 500KB JPEG ≈ 667KB string23// - Large API Connector bodies consume more Bubble WU24// - Bubble API Connector may have body size limits — test with your image sizes25// - For large images, resize client-side before upload (use Bubble's Image optimizer)26// - Consider Vertex AI (managed) over self-hosted TF Serving for image modelsPro tip: Resize images before sending them to TensorFlow Serving — most image classification models expect inputs at specific resolutions (e.g., 224x224 for MobileNet, 299x299 for InceptionV3). Sending a full-resolution smartphone photo (4000x3000 pixels) massively increases the base64 payload size and may cause timeout errors. Bubble's file uploader has width/height constraints you can set to enforce upload dimensions.
Expected result: Image uploads are converted to base64 and sent to TensorFlow Serving's prediction endpoint. The model returns classification results that Bubble displays as human-readable labels with confidence scores.
Test the full integration, version management, and monitoring
Before going live, validate the complete pipeline and set up monitoring for your self-hosted TensorFlow Serving endpoint: **1. Initialize call validation:** Re-run the Initialize call in Bubble's API Connector with your current HTTPS endpoint. Confirm it returns the `predictions` array. If you updated your model (retrained with new features or classes), the response shape may have changed — re-initialize to update Bubble's detected response fields. **2. Model version testing:** If you have multiple model versions (v1, v2, etc.), test both via their version-specific endpoints before switching Bubble to the new URL. TensorFlow Serving serves the highest version number by default — to pin to a specific version, use `/v1/models/{name}/versions/{version}:predict`. **3. Input schema drift:** If your model is retrained with new feature columns or renamed tensors, the old Bubble API call body will silently send wrong field names. TensorFlow Serving may return predictions based on defaults or zeros rather than returning an explicit error. Always validate predictions with known-good inputs after a model update. **4. HTTPS certificate renewal:** Let's Encrypt certificates expire every 90 days. Certbot sets up automatic renewal, but verify renewal is configured: `sudo certbot renew --dry-run`. If the certificate expires, Bubble's API Connector will return a TLS error and all prediction calls will fail silently. **5. WU cost awareness:** Each TensorFlow Serving prediction call from Bubble consumes Workload Units. For high-traffic apps where many users request predictions frequently, cache prediction results in Bubble's database: store inputs + outputs in a Predictions data type, and check for an existing prediction before calling TF Serving. This reduces API calls for duplicate or similar inputs. **6. Privacy rules:** Add Bubble Privacy rules to any Prediction data type you create — ensure each user can only read their own prediction records. RapidDev's team has shipped custom ML model integrations in Bubble apps across multiple industries — for complex model serving architecture or scaling guidance, reach out at rapidevelopers.com/contact.
1// Model version endpoints:2// Latest version (auto):3// POST /v1/models/{model_name}:predict4//5// Specific version:6// POST /v1/models/{model_name}/versions/2:predict7//8// Model status check:9// GET /v1/models/{model_name}10// Response: {"model_version_status": [{"version": "2", "state": "AVAILABLE"}]}1112// Prediction caching in Bubble:13// Data type: Prediction_Cache14// - input_hash (text) — e.g., MD5 or concatenated input values15// - output (text) — stored prediction result16// - model_version (text)17// - created_at (date)18//19// Pre-call workflow:20// 1. Build input_hash from current inputs21// 2. Search Prediction_Cache where input_hash = [hash] and model_version = current_version22// 3a. If found: use cached output (no TF Serving call)23// 3b. If not found: call TF Serving, store result in Prediction_Cache2425// Certificate renewal check:26// sudo certbot renew --dry-run27// (Run this monthly to confirm automatic renewal is working)Pro tip: Set up a simple uptime monitoring check for your TensorFlow Serving HTTPS endpoint using a free service like UptimeRobot. Point it at your health check URL (`GET https://your-domain.com/v1/models/model_name`) — if the endpoint goes down (server restart, certificate expiry, memory overload), you get an immediate alert rather than discovering the outage when users report missing predictions.
Expected result: The full Bubble-to-TensorFlow-Serving pipeline is tested with known-good inputs and outputs match expectations. Certificate renewal is confirmed automatic. Model version updates have a tested deployment process. Prediction caching is implemented to reduce redundant API calls.
Common use cases
Customer churn prediction for a SaaS dashboard
A Bubble SaaS admin dashboard calls a trained churn prediction model each time an admin views a customer account. The model receives usage_frequency, days_since_last_login, support_ticket_count, and subscription_age as inputs and returns a churn probability. Bubble displays this score as a risk badge (low/medium/high) next to each customer record.
When an admin opens a Customer detail page, trigger a Backend Workflow: POST to TensorFlow Serving with instances containing the customer's last_login_days, weekly_usage, ticket_count, and subscription_months from the Bubble database. Parse the returned predictions[0] probability and display as a color-coded risk indicator on the page.
Copy this prompt to try it in Bubble
Image classification for uploaded photos
Users upload product photos through a Bubble file upload element. A Backend Workflow base64-encodes the image and sends it to a TensorFlow image classification model. The model returns a category label and confidence score — Bubble automatically populates the product category dropdown based on the predicted class, reducing manual categorization work.
After file upload, trigger a Backend Workflow: fetch the uploaded file URL, encode it as base64, POST to TensorFlow Serving with instances[0] containing the base64 image string. Extract predictions[0][0] as the predicted class index, map it to a category name using a Bubble Option Set, and set the Product category dropdown to the predicted value.
Copy this prompt to try it in Bubble
Real-time pricing prediction for a marketplace
A marketplace Bubble app uses a price prediction model to suggest listing prices. Sellers input the item's condition, category, age, and comparable sales data — the model returns a recommended price range. Bubble displays this suggestion as the default price before the seller finalizes their listing.
When a seller completes the product details form and clicks 'Get Price Suggestion,' POST to TensorFlow Serving with instances containing condition_score, category_id, age_months, and recent_sales_average. Display the returned predicted_price (scaled back from the model's normalized output) as the suggested price in the price input field.
Copy this prompt to try it in Bubble
Troubleshooting
Bubble API Connector returns TLS/HTTPS error or 'connection refused'
Cause: TensorFlow Serving is running on plain HTTP at port 8501 and Bubble's API Connector blocks non-HTTPS connections. This is the most common setup error — TensorFlow Serving's HTTP endpoint is not accessible via HTTPS without a reverse proxy or cloud load balancer.
Solution: Add an nginx reverse proxy with a Let's Encrypt SSL certificate in front of TensorFlow Serving's port 8501, or deploy behind a cloud load balancer with TLS termination. Your Bubble API Connector base URL must start with `https://` not `http://`. After setting up HTTPS, verify with `curl -X GET https://your-domain.com/v1/models/model_name` before configuring Bubble.
TensorFlow Serving returns 400 Bad Request for prediction calls
Cause: The field names in the instances array do not match the model's SignatureDef input tensor names. This is the most common prediction error — TensorFlow Serving is strict about exact field name matching. A field named `input_layer` in the model will not accept data sent as `input` or `feature_1`.
Solution: Run `saved_model_cli show --dir /path/to/savedmodel --tag_set serve --signature_def serving_default` to see the exact input tensor names. Update the Bubble API Connector body to use those exact names. Re-initialize the call with test values using the corrected field names.
1// Find your model's input tensor names:2// saved_model_cli show --dir /model --tag_set serve --signature_def serving_default3//4// Example output showing the input name to use:5// inputs['serving_default_dense_input:0'] tensor_info:6// name: serving_default_dense_input:07//8// In API Connector body, use the key name WITHOUT the ':0' suffix:9// { "instances": [{ "dense_input": [0.5, 1.2, 0.8] }] }Predictions return unexpected values or zeros for all inputs
Cause: Input feature values are outside the expected range for the model (if the model was trained on normalized data expecting 0-1 values, sending raw values like 500 days or 1000 tickets produces meaningless predictions). Also occurs when the model is loaded but not yet in AVAILABLE state when the call fires.
Solution: Check the model's training data preprocessing — if features were normalized during training (e.g., using StandardScaler or MinMaxScaler), apply the same normalization before sending values to TF Serving. Verify the model status via GET /v1/models/{name} returns 'AVAILABLE' before sending predictions. Build the normalization calculation into your Bubble workflow: `(raw_value - min_value) / (max_value - min_value)` for min-max normalization.
Initialize call returns 'There was an issue setting up your call'
Cause: Bubble cannot detect the response schema because the Initialize call failed. This typically means the TensorFlow Serving endpoint is unreachable (wrong URL, nginx not running, port blocked by firewall), the API key check failed (wrong X-Api-Key header value), or the model name in the URL path is incorrect.
Solution: Test the endpoint directly with curl from your local machine: `curl -X POST https://your-domain.com/v1/models/model_name:predict -H 'Content-Type: application/json' -H 'X-Api-Key: secret' -d '{"instances": [{"input": 1.0}]}'`. If curl fails, debug the infrastructure before returning to Bubble. Common issues: firewall blocking port 443, nginx misconfiguration, or TF Serving not running (check `docker ps` or `systemctl status tensorflow-serving`).
Large image inputs cause API Connector timeout errors
Cause: Base64-encoded images create large JSON bodies that exceed Bubble's API Connector body size limits or take too long to process, causing the API call to time out before TensorFlow Serving responds.
Solution: Resize images before uploading in Bubble using the File Uploader's built-in size constraints. Most image classification models work with images under 500KB — set a maximum file size in the File Uploader settings. For larger images, consider using a Cloud Function as an intermediary that resizes the image before passing it to TensorFlow Serving. Alternatively, switch to Vertex AI (managed) for image models which handles larger payloads and has configurable timeout settings.
Best practices
- Always add HTTPS via nginx or a cloud load balancer before attempting to connect Bubble to TensorFlow Serving — Bubble's API Connector blocks HTTP connections and TensorFlow Serving's default HTTP port 8501 cannot be used directly.
- Verify your model's input tensor names using `saved_model_cli` before configuring the Bubble API Connector body — wrong field names cause silent 400 errors or incorrect predictions without clear diagnostic messages.
- Apply the same feature normalization in Bubble that was used during model training — if your scaler transformed raw features to 0-1 ranges, Bubble must calculate those same transformations before sending values to TensorFlow Serving.
- Cache prediction results in a Bubble Predictions data type keyed by input values — this avoids redundant calls to TensorFlow Serving for identical inputs and reduces both Bubble Workload Unit consumption and server load on your VM.
- Run the TensorFlow Serving health check endpoint (GET /v1/models/{name}) in a periodic Bubble Backend Workflow and store the result — if the model status changes from AVAILABLE, alert an admin before users experience prediction failures.
- Add Bubble Privacy rules to any data type that stores prediction inputs or outputs — prevent users from reading each other's prediction history by filtering to records created by the Current User.
- For production image model integrations, consider migrating from self-hosted TensorFlow Serving to Vertex AI (Google Cloud AI Platform) — Vertex AI handles HTTPS, auto-scaling, and monitoring automatically without requiring you to manage a VM and nginx configuration.
Alternatives
Google Cloud AI Platform (Vertex AI) is the managed alternative to self-hosting TensorFlow Serving. Vertex AI handles HTTPS, auto-scaling, health monitoring, and model versioning — no nginx or VM management required. The trade-off is GCP pricing per prediction request and a more complex authentication flow (service account Bearer tokens). Choose Vertex AI if you want managed scaling; choose self-hosted TensorFlow Serving for direct infrastructure control and lower per-prediction costs.
OpenAI GPT is an off-the-shelf language model API with a simple persistent API key — no infrastructure setup, no model training, no HTTPS configuration. TensorFlow Serving is for custom-trained models (computer vision, tabular prediction, proprietary NLP) that cannot be replaced by a general-purpose LLM. Choose OpenAI GPT for text generation and conversation; choose TensorFlow Serving when you have proprietary trained models with specific input-output schemas.
Azure Machine Learning is the Microsoft-managed equivalent to Vertex AI for TensorFlow models — it hosts your model, manages HTTPS, and scales automatically. Azure ML inference endpoints support simpler API key authentication alongside Azure AD tokens, which can be easier than the full JWT flow required for Vertex AI. Choose Azure ML if your organization uses Microsoft Azure; choose self-hosted TensorFlow Serving for maximum cost control.
Frequently asked questions
What is the difference between TensorFlow and TensorFlow Serving?
TensorFlow is a Python library for training machine learning models — you use it in Python scripts or Jupyter notebooks. It does not expose an HTTP API by itself. TensorFlow Serving is a separate production server that loads a trained SavedModel and wraps it in an HTTP REST API. When this guide says 'connect Bubble to TensorFlow,' it means connecting to a TensorFlow Serving instance deployed on a server — not to TensorFlow the library.
Can I run TensorFlow Serving locally on my laptop and connect Bubble to it?
No. Bubble's servers need to reach your TensorFlow Serving endpoint over the public internet, and localhost:8501 on your laptop is not publicly accessible. You must deploy TensorFlow Serving on a cloud VM (GCP, AWS, DigitalOcean, Render, etc.) with a public IP address and HTTPS enabled. If you need a temporary public URL for testing, use a tunneling tool like ngrok — but this is only for development, not production.
How do I know what field names to use in the instances array?
Run `saved_model_cli show --dir /path/to/your/savedmodel --tag_set serve --signature_def serving_default` to inspect the model's SignatureDef. The output shows each input tensor's name — use these exact names as field keys in the instances object in your Bubble API Connector body. Wrong names are the most common cause of 400 errors and unexpected prediction outputs.
Do I need to normalize features before sending them to TensorFlow Serving?
It depends on whether normalization was included in your model's computational graph. If your model was saved with a preprocessing layer that handles normalization internally (e.g., using Keras Normalization layers), you can send raw values. If normalization was done externally during training using scikit-learn's StandardScaler or MinMaxScaler, you must apply the same transformation in your Bubble workflow before sending features to TF Serving. Sending unnormalized values to a model trained on normalized data produces meaningless predictions.
What happens if TensorFlow Serving goes down while my Bubble app is live?
Bubble's API Connector call will return an error (connection timeout or 503 Service Unavailable). Without error handling, users will see an empty or frozen loading state. Add error handling to your Bubble workflow: catch non-200 responses and show a user-friendly message like 'Prediction service is temporarily unavailable — please try again in a few minutes.' Set up uptime monitoring (UptimeRobot or similar) to alert you immediately when the TensorFlow Serving instance goes down.
Can I use TensorFlow.js to run model inference in the browser from Bubble?
No. Bubble does not support arbitrary JavaScript execution with external library imports in a way that would allow TensorFlow.js to load and run a model. TensorFlow.js requires importing a large JavaScript library and loading model weights — neither is possible within Bubble's workflow editor or standard Bubble elements. Server-side predictions via TensorFlow Serving's REST API are the only supported path for Bubble integrations.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation