# How to Integrate Lovable with TensorFlow

- Tool: Lovable
- Difficulty: Intermediate
- Time required: 45 minutes
- Last updated: March 2026

## TL;DR

Integrate TensorFlow.js with your Lovable app by running custom ML models either client-side in the browser or server-side in a Supabase Edge Function. Host model files in Supabase Storage, load them with @tensorflow/tfjs or @tensorflow/tfjs-node, and run predictions without exposing proprietary model weights. Use TensorFlow when you own and control the model, unlike OpenAI where you call a pre-trained LLM API you do not own.

## Serve custom TensorFlow.js ML models from your Lovable app

TensorFlow.js brings machine learning inference directly to JavaScript environments — browsers and Node.js/Deno runtimes alike. Unlike calling a managed LLM API such as OpenAI GPT, where you send text to a cloud service and receive a response, TensorFlow lets you run models you trained and own. This matters when your ML model is proprietary, when you need to run inference on sensitive data that must not leave your infrastructure, or when you need a specialized model for tabular data classification, image recognition, or time-series forecasting rather than general-purpose text generation.

In a Lovable app, TensorFlow.js integrates through two paths depending on model size and privacy requirements. Small models (under 5 MB) can run entirely client-side in the browser — no server needed, no API calls, near-zero latency predictions. Larger models or models whose weights must remain private are better served from a Supabase Edge Function that loads the model from a private Supabase Storage bucket and runs inference server-side. The Edge Function approach ensures your model weights are never downloaded to the client browser, protecting proprietary training data and preventing model extraction attacks.

The key differentiator from other AI integrations is ownership. When you use OpenAI GPT or Google Vertex AI, you are renting access to someone else's model. With TensorFlow.js, you own the model entirely — you trained it, you host it, and you control who can run inference against it. This makes TensorFlow the right choice for startups with unique ML models, healthcare or finance applications with data privacy requirements, or any use case where the ML model itself is a core competitive asset.

## Before you start

- A Lovable account with an active Lovable Cloud project
- A trained TensorFlow.js model exported in the layers or graph format (model.json + shard files), or a pre-built TF.js model from TensorFlow Hub
- Your Supabase project URL and service role key from Cloud → Secrets (for uploading model files to Storage)
- Basic understanding of your model's input shape and expected preprocessing steps
- If serving server-side: your model files uploaded to a Supabase Storage bucket (use the Supabase Storage dashboard to upload)

## Step-by-step guide

### 1. Upload your TensorFlow.js model files to Supabase Storage

Before writing any Edge Function code, your TensorFlow.js model files need to be accessible at runtime. A TF.js model exported in the layers format consists of a model.json descriptor file and one or more binary weight shard files (e.g., group1-shard1of1.bin). These files must be stored somewhere the Edge Function can fetch them — Supabase Storage is the right place because it sits within the same project infrastructure and supports both public and private bucket access.

To upload model files, open your Lovable project and click the '+' icon next to Preview to open the Cloud panel. Click the 'Storage' section. Click 'New bucket' and name it 'models'. For a private model (one whose weights should not be downloaded by end users), leave 'Public bucket' unchecked — this means only requests with a valid service role key can read the files. For a pre-trained public model that is not proprietary, you can make the bucket public and load it directly from the browser without an Edge Function.

Once the bucket is created, create a folder path like 'product-classifier/' inside it. Upload your model.json and all .bin shard files into this folder. The complete storage path will be: models/product-classifier/model.json. Note this exact path — you will use it in your Edge Function code.

If you do not yet have a trained TF.js model, you can use a pre-converted model from TensorFlow Hub (https://tfhub.dev) — many models are available in TF.js format. For testing the integration end-to-end, the MobileNet image classification model is a good starting point and is small enough to demonstrate the full flow.

**Expected result:** Your model.json and weight shard .bin files appear in the Supabase Storage dashboard under the 'models' bucket. Clicking on model.json shows a valid JSON file with a 'modelTopology' key.

### 2. Store the Supabase service role key in Cloud → Secrets

The Edge Function needs to fetch model files from your private Supabase Storage bucket at runtime. Reading from a private bucket requires the Supabase service role key — a credential that bypasses Row Level Security and can read any storage object. This key must never appear in frontend code or be exposed to the browser.

To add the secret, click the '+' icon next to Preview to open the Cloud panel, then click the 'Secrets' tab. Click 'Add new secret'. Add the following:

- Name: SUPABASE_URL — Value: your Supabase project URL (format: https://xxxxxxxxxxxx.supabase.co)
- Name: SUPABASE_SERVICE_ROLE_KEY — Value: your service role key from Supabase dashboard → Project Settings → API → service_role secret

Lovable Cloud automatically injects SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY into Edge Functions for projects using the built-in Lovable Cloud database. If your project uses Lovable Cloud (the default), these may already be available as built-in platform secrets. Verify by checking whether the secrets already exist before adding duplicates.

Lovable's security infrastructure blocks approximately 1,200 hardcoded API keys per day, but the safest practice is to never paste service role keys anywhere except the Secrets panel. The service role key gives full database and storage access with no RLS restrictions — treat it with the same care as a database root password.

**Expected result:** SUPABASE_SERVICE_ROLE_KEY is stored in Cloud → Secrets with a masked value. The key is not visible in any Lovable chat, code editor view, or Git commit.

### 3. Create the TensorFlow.js inference Edge Function

Now create the Edge Function that loads your model from Supabase Storage and runs inference. The Deno runtime in Supabase Edge Functions supports WebAssembly and the TensorFlow.js Deno-compatible package. The approach is: fetch the model.json file and its weight shards from Supabase Storage using signed URLs or service-role-authenticated requests, use tf.loadLayersModel() or tf.loadGraphModel() to load the model into memory, preprocess the input, run model.predict(), and return the result as JSON.

TensorFlow.js in Deno requires importing from the correct CDN. Use the tfjs package from esm.sh which bundles the CPU backend. The model loading uses tf.io.browserHTTPRequest() to fetch from a URL — you generate a short-lived signed URL for the model.json file using the Supabase Storage SDK, which the TF.js loader can then follow to download all associated weight shards.

Note that loading a model cold (first invocation after the Edge Function has been idle) can take 2-10 seconds for large models. For production use cases with latency requirements, consider keeping the Edge Function warm with a scheduled ping, or caching the loaded model in a module-level variable (Deno module cache persists for the lifetime of the Edge Function instance). Paste the Lovable prompt below into chat to scaffold the function, then customize the preprocessing logic for your specific model.

```
// supabase/functions/tf-predict/index.ts
import * as tf from 'https://esm.sh/@tensorflow/tfjs@4.20.0';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

// Module-level model cache — persists across requests in the same instance
let cachedModel: tf.LayersModel | null = null;

async function getModel(supabaseUrl: string, serviceRoleKey: string): Promise<tf.LayersModel> {
  if (cachedModel) return cachedModel;

  const supabase = createClient(supabaseUrl, serviceRoleKey);

  // Generate a signed URL for the model.json (valid for 5 minutes)
  const { data, error } = await supabase.storage
    .from('models')
    .createSignedUrl('product-classifier/model.json', 300);

  if (error || !data?.signedUrl) {
    throw new Error(`Failed to get model URL: ${error?.message}`);
  }

  // TF.js loads model.json and automatically fetches weight shards from the same directory
  cachedModel = await tf.loadLayersModel(data.signedUrl);
  console.log('Model loaded and cached');
  return cachedModel;
}

Deno.serve(async (req) => {
  if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders });

  try {
    const { input, inputShape } = await req.json() as {
      input: number[];
      inputShape: number[];
    };

    if (!input || !inputShape) {
      return new Response(JSON.stringify({ error: 'input and inputShape are required' }), {
        status: 400,
        headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      });
    }

    const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
    const serviceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;

    const model = await getModel(supabaseUrl, serviceRoleKey);

    // Create input tensor and run prediction
    const tensor = tf.tensor(input, inputShape);
    const prediction = model.predict(tensor) as tf.Tensor;
    const values = await prediction.data();

    // Cleanup tensors to avoid memory leaks
    tensor.dispose();
    prediction.dispose();

    return new Response(JSON.stringify({ predictions: Array.from(values) }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  } catch (error) {
    console.error('Inference error:', error);
    return new Response(JSON.stringify({ error: String(error) }), {
      status: 500,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
});
```

**Expected result:** The tf-predict Edge Function deploys successfully. A test POST request with a sample input array returns a 'predictions' array. Cloud → Logs shows 'Model loaded and cached' on the first request and subsequent requests are faster.

### 4. Call the prediction Edge Function from your React frontend

With the Edge Function deployed, build the React component that calls it. Use supabase.functions.invoke() to call the tf-predict function, passing the input data as a serialized JSON body. The input preprocessing (converting an image to a flat float32 array, normalizing pixel values to 0-1, etc.) happens client-side in React before the API call — this keeps the preprocessing logic visible and debuggable without touching the model-serving code.

For image classification, the frontend flow is: user selects an image → draw it to a canvas element at the model's expected input size (e.g., 224x224 for MobileNet-based models) → extract pixel data as a flat Uint8ClampedArray → normalize values to 0-1 by dividing by 255 → send to the Edge Function → display the returned prediction scores with labels.

For tabular models, the preprocessing is simpler: collect form field values, normalize each feature using the same scaling parameters used during training (often min-max or z-score normalization), build the input array, and send it.

Ask Lovable to build the complete frontend component with the preprocessing and display logic. Describe the model's inputs (what the user provides), the expected output format (class labels and scores, or a regression value), and the UI design you want. Lovable will wire up the supabase.functions.invoke() call and generate appropriate loading/error states.

**Expected result:** The image classifier UI allows users to upload a photo, preprocesses it client-side, calls the Edge Function, and displays prediction results. The full round-trip from upload to displayed prediction completes within 3-5 seconds including cold model load.

### 5. Add error handling, logging, and input validation

Production ML inference endpoints need defensive coding that goes beyond a basic happy path. Add server-side input validation to your Edge Function to catch common problems: verify the input array has the expected number of elements (length must match the product of all inputShape dimensions), check that all values are finite numbers (not NaN or Infinity, which TensorFlow will silently propagate through the model producing nonsense outputs), and enforce a maximum input size to prevent memory exhaustion attacks.

For logging, add structured console.log statements at key points — model load time, inference duration, and output summary. Cloud → Logs in the Lovable Cloud panel shows these logs in real-time. Monitoring inference latency is important for TensorFlow models because it can vary significantly based on model size, whether the model is cached, and Deno instance warm-up time.

For client-facing error messages, distinguish between input validation errors (return 400 with a descriptive message the UI can display), model loading failures (return 503 with a retry-later message), and inference errors (return 500). Never expose raw TensorFlow error stack traces to the client — they can reveal model architecture details.

For complex production ML deployments with high-traffic requirements, custom preprocessing pipelines, or A/B testing between model versions, RapidDev's team can help architect a scalable model serving strategy on top of Lovable Cloud's Edge Function infrastructure.

```
// Input validation helper — add to the Edge Function
function validateInput(input: unknown, inputShape: unknown): string | null {
  if (!Array.isArray(input)) return 'input must be an array of numbers';
  if (!Array.isArray(inputShape)) return 'inputShape must be an array of integers';

  const expectedSize = (inputShape as number[]).reduce((a, b) => a * b, 1);
  if ((input as number[]).length !== expectedSize) {
    return `input length ${(input as number[]).length} does not match inputShape product ${expectedSize}`;
  }

  const hasInvalid = (input as number[]).some((v) => !Number.isFinite(v));
  if (hasInvalid) return 'input contains NaN or Infinity values';

  return null; // valid
}

// Usage in the handler:
// const validationError = validateInput(input, inputShape);
// if (validationError) return new Response(JSON.stringify({ error: validationError }), { status: 400, ... });
```

**Expected result:** The Edge Function rejects invalid inputs with clear 400 error messages. Cloud → Logs shows inference duration per request. The React frontend displays user-friendly error messages for validation failures and service errors.

## Best practices

- Always dispose tensors after inference using tf.tidy() or explicit tensor.dispose() calls — TensorFlow.js does not garbage collect tensors automatically and memory leaks will crash the Edge Function after repeated requests.
- Cache the loaded model in a module-level variable so it is only downloaded from Supabase Storage once per Edge Function instance — model loading is expensive and should not happen on every request.
- Store model files in a private Supabase Storage bucket and access them via service-role-authenticated signed URLs — never expose model weights directly to the browser unless the model is public and non-proprietary.
- Validate input tensor shapes and value ranges server-side before running inference — reject inputs with wrong dimensions or non-finite values (NaN, Infinity) with a 400 error and a descriptive message.
- Quantize model weights to float16 or int8 before uploading using the TensorFlow.js converter — quantization typically reduces model size by 2-4x with minimal accuracy loss, dramatically improving cold start time.
- Log inference duration and model prediction confidence scores to Cloud → Logs on every request — this data helps you catch degraded model performance and identify input distributions that differ from training data.
- Use separate Edge Function instances for different models rather than loading multiple models in one function — each model has independent caching, scaling, and cold start behavior, making debugging and performance optimization simpler.

## Use cases

### Run a custom image classifier for user-uploaded product photos

Users upload product photos to your Lovable app, and the app automatically classifies them into categories using a TensorFlow.js model you trained on your own product catalog. The model runs server-side in an Edge Function, receives a base64-encoded image, preprocesses the tensor, runs inference, and returns the predicted category with a confidence score. Model weights stay private in Supabase Storage.

Prompt example:

```
Create a Supabase Edge Function that accepts a base64-encoded image, loads a TensorFlow.js model from Supabase Storage at the path 'models/product-classifier/model.json', preprocesses the image into a 224x224 tensor, runs inference, and returns the top-3 predicted categories with confidence scores. Then build a React component where users drag-and-drop or upload a product image and see the predicted category and confidence displayed below it.
```

### Predict churn probability from tabular user data

A trained TensorFlow.js tabular classification model predicts whether a user is likely to churn based on their activity data — days since last login, feature usage, subscription tier, and support tickets. The Edge Function reads user data from Supabase, runs it through the model, and stores the churn probability score back to the database for the sales team to action. No raw user data leaves your infrastructure.

Prompt example:

```
Create a Supabase Edge Function called 'predict-churn' that queries the users table for activity metrics (last_login, feature_usage_count, subscription_tier, support_tickets), loads a TensorFlow.js model from Supabase Storage at 'models/churn/model.json', runs each user's features through the model, and saves the predicted churn_probability back to the users table. Build a dashboard page that displays users sorted by churn probability with a red/yellow/green risk indicator.
```

### Real-time sentiment analysis on user-submitted text

A compact TensorFlow.js text classification model trained on your domain-specific data runs client-side in the browser to score sentiment or classify support tickets without any API call. Since the model is small enough to download once and cache, subsequent predictions are instant. This is ideal for live form validation — showing sentiment feedback as users type a review or support message.

Prompt example:

```
Build a feedback form where the sentiment of the user's text is analyzed in real-time as they type. Load a TensorFlow.js Universal Sentence Encoder model client-side using the @tensorflow-models/universal-sentence-encoder package from esm.sh. After the user stops typing for 500ms, run the input through the encoder and a simple classifier to produce a positive/neutral/negative sentiment score. Display a colored sentiment indicator next to the textarea that updates live.
```

## Troubleshooting

### Edge Function logs 'Error: Failed to get model URL' or 'storage/object not found'

Cause: The model file path in the createSignedUrl call does not match the actual path in Supabase Storage, the bucket name is wrong, or the service role key does not have storage read permissions.

Solution: Open Cloud → Storage in the Lovable panel and navigate to the 'models' bucket. Verify the exact folder and file path — paths are case-sensitive and slashes matter. Copy the exact path shown in the Storage UI and paste it into your Edge Function. Also verify that SUPABASE_SERVICE_ROLE_KEY in Cloud → Secrets matches the service_role key from your Supabase project settings.

### Prediction returns NaN or all-zero values

Cause: The input tensor shape does not match the model's expected input shape, the input values are not normalized to the range the model was trained on (typically 0-1 for image models), or the model was saved in graph format but loaded with tf.loadLayersModel() instead of tf.loadGraphModel().

Solution: Log the model.inputs[0].shape to confirm what input shape the model expects. Verify that your input array length equals the product of the expected shape dimensions. For image classifiers, confirm pixel values are divided by 255 before sending. If the model was exported with model.save() in TF SavedModel format, use tf.loadGraphModel() instead of tf.loadLayersModel().

```
// Check model input shape before running inference
console.log('Expected input shape:', JSON.stringify(model.inputs[0].shape));
console.log('Provided input shape:', JSON.stringify(inputShape));
console.log('Input array length:', input.length);
```

### Edge Function times out on first request (504 Gateway Timeout)

Cause: Loading a large model from Supabase Storage on cold start takes longer than the Edge Function timeout allows. Large models (over 20 MB) can take 10-30 seconds to download and initialize, exceeding the default timeout.

Solution: Reduce model size by quantizing weights to int8 or float16 using the TensorFlow.js converter (tfjs-converter --quantize_float16 or --quantize_uint8 flags). Alternatively, split inference into a model-loading warm-up endpoint called at app startup and a separate prediction endpoint. For very large models, consider using Google Vertex AI or a dedicated model serving container instead of Edge Functions.

### Memory leak — Edge Function crashes after many requests with 'Out of memory'

Cause: TensorFlow.js tensors created during inference are not being disposed. Each predict() call allocates memory for input tensors, output tensors, and intermediate activations. Without explicit disposal, these accumulate until the Deno process runs out of memory.

Solution: Wrap inference code in tf.tidy() which automatically disposes all tensors created within the block. Only tensors returned from tf.tidy() are kept. Alternatively, call tensor.dispose() and prediction.dispose() explicitly after extracting the values with await prediction.data().

```
// Use tf.tidy() to automatically dispose intermediate tensors
const values = tf.tidy(() => {
  const tensor = tf.tensor(input, inputShape);
  const prediction = model.predict(tensor) as tf.Tensor;
  return prediction.dataSync(); // synchronous read inside tidy
});
```

## Frequently asked questions

### Should I run TensorFlow.js client-side in the browser or server-side in an Edge Function?

Run models client-side if they are small (under 5 MB), publicly available (not proprietary), and do not process sensitive data. Client-side inference has zero latency overhead and requires no Edge Function. Run models server-side in an Edge Function if the model is proprietary (you want to protect the weights), larger than 5-10 MB (too slow to download on mobile connections), or needs to access private Supabase data as part of the inference pipeline.

### How do I convert a Python TensorFlow/Keras model to TensorFlow.js format?

Use the tensorflowjs_converter CLI tool: pip install tensorflowjs, then run tensorflowjs_converter --input_format=keras your_model.h5 output_dir/ for Keras models, or tensorflowjs_converter --input_format=tf_saved_model saved_model_dir/ output_dir/ for TF SavedModel format. This produces a model.json and weight shard .bin files that you upload to Supabase Storage. The converter is a Python tool run locally, not inside Lovable.

### What model file formats does TensorFlow.js support?

TensorFlow.js supports two formats: the Layers format (created from Keras models with model.save()) loaded with tf.loadLayersModel(), and the Graph format (created from TF SavedModels or TFLite models with the converter) loaded with tf.loadGraphModel(). Both produce a model.json descriptor and binary weight shard files. Use Layers format for models you trained with Keras; use Graph format for models exported from TensorFlow's SavedModel format or downloaded from TensorFlow Hub.

### Can I use TensorFlow.js pre-trained models like MobileNet or BERT without training my own?

Yes. TensorFlow Hub (tfhub.dev) provides hundreds of pre-trained TF.js models for image classification, object detection, text embedding, pose estimation, and more. You can load these directly from their URL using tf.loadGraphModel(tfhub_url) without uploading to Supabase Storage, or download the model files and host them in your own Storage bucket for reliability and to avoid dependency on an external CDN.

### Why does TensorFlow differ from OpenAI or Google Vertex AI?

TensorFlow.js is a model execution framework — it runs inference on models you provide. OpenAI and Google Vertex AI are services where you send data to their servers and receive predictions from models they host and manage. TensorFlow gives you full ownership and control of the model but requires you to train it, host it, and manage inference. OpenAI/Vertex AI require no model training or infrastructure management but the model is not yours and you pay per API call.

---

Source: https://www.rapidevelopers.com/lovable-integration/tensorflow
© RapidDev — https://www.rapidevelopers.com/lovable-integration/tensorflow
