Lovable + reelsdk

Render faceless shorts from a Lovable app

Lovable Cloud runs your backend as Supabase edge functions: 256 MB of memory, 2 s of CPU per request and no subprocesses, so ffmpeg has nowhere to run. reelsdk renders on its own machines. One call from an edge function turns a topic into a narrated, captioned short for $0.25 up to 60 s, and a signed webhook puts the mp4 in your videos table about 100 s later. The first $1 is free.

  • One prompt sets up the table, two edge functions and the page
  • A short renders in about 100 s on reelsdk machines
  • Your key stays a write-only Lovable secret, never in the browser
What Lovable apps run into, in the browser and in the edge function

Error: failed to import ffmpeg-core.js

Spawning subprocesses is not allowed on Supabase Edge Runtime.

Sources: ffmpeg.wasm source, Supabase Edge Runtime source

The same job as one call to reelsdk

const REELSDK = "https://api.reelsdk.com";

const res = await fetch(`${REELSDK}/v1/shorts?wait=0`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${Deno.env.get("REELSDK_KEY")}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ topic, style: "cinematic", webhook_url }),
});
const { job } = await res.json();
202 right away with job.id, then a signed webhook fills in the mp4 link about 100 s later

Why ffmpeg breaks here

Two places to put ffmpeg, and neither works

Lovable Cloud builds its backend on Supabase, so the functions Lovable writes for you are Supabase Edge Functions running on Deno. Supabase documents 256 MB of memory and 2 s of CPU per request, and the runtime refuses to start a native binary with Spawning subprocesses is not allowed on Supabase Edge Runtime. An encode heavy enough to matter ends in HTTP 546 with WORKER_RESOURCE_LIMIT.

The other place is the browser. ffmpeg.wasm downloads a core of about 31 MB and encodes on the visitor's own device. That's fine for a desktop tool that trims one short clip. On a phone it stalls the tab, it stops when the user closes the page, and it can't make videos in the background, on a schedule or for many users at once.

Lovable Cloud next to reelsdk

Lovable Cloud next to reelsdk
LimitLovable Cloudreelsdk
Memory256 MB per edge functionRenders on reelsdk machines, inputs up to 500 MB each
CPU time2 s per requestUp to 10 min of run time per job
Native ffmpegSubprocesses are not allowedffmpeg 8.1 on the server, raw arguments through POST /v1/ffmpeg
In the browserffmpeg.wasm, about 31 MB per visitorNothing to download, the page shows an mp4
Long jobsThe function has to return while the render runs?wait=0 plus a signed webhook, 3 delivery attempts

Lovable limits from Supabase Edge Functions limits, checked October 2026.

Set it up

One prompt, two edge functions, one table

You need a reelsdk key and its webhook secret. Lovable writes the code from the prompt below. The snippets after it show what the two functions should look like, so you can check its work before you publish.

  1. Paste the prompt into Lovable

    The prompt asks for a videos table with row level security and realtime, a create-short function that starts a render, a reelsdk-webhook function that receives it, and a page that lists your videos. Lovable writes and deploys edge functions from chat, there is no create button.

    Prompt for Lovable
    Add faceless short videos to this app, rendered by reelsdk (https://reelsdk.com).
    
    1. Ask me for two secrets: REELSDK_KEY and REELSDK_WEBHOOK_SECRET. Never use them in frontend code.
    2. Create a table "videos": id uuid, user_id uuid, topic text, job_id text, status text default 'queued', video_url text, created_at timestamptz. Turn on RLS so users only read their own rows, and enable realtime on the table.
    3. Create an edge function "create-short" for signed-in users. It takes { topic }, POSTs to https://api.reelsdk.com/v1/shorts?wait=0 with the header "Authorization: Bearer REELSDK_KEY" and the JSON { "topic": topic, "style": "cinematic", "duration_s": 45, "webhook_url": "<this project's functions URL>/reelsdk-webhook" }. The response is JSON { job } with HTTP 202. Insert a videos row with job_id = job.id and status = job.status.
    4. Create an edge function "reelsdk-webhook" with JWT verification turned off. Read the raw body with req.text() before parsing. Verify the Reel-Signature header (format t=<unix seconds>,v1=<hex>): it is the HMAC-SHA256 of "<t>.<raw body>" keyed with REELSDK_WEBHOOK_SECRET. Return 401 if it doesn't match or t is more than 300 seconds old. Then update the videos row where job_id = job.id with status = job.status and video_url = job.outputs.video.
    5. On the dashboard, add a topic field and a Generate button that calls create-short, and list the user's videos with a player once the status is "succeeded".
  2. Add the key and the webhook secret

    When Lovable asks for them, paste both values into the secure form in the chat, or add them under More, Settings, Secrets. Lovable encrypts secrets, injects them into edge functions and never sends them to the browser. They are write-only, and a remix doesn't copy them, so add them again in a remixed project.

    Lovable secrets
    REELSDK_KEY
    rk_live_...
    REELSDK_WEBHOOK_SECRET
    whsec_...
  3. Check the create-short function

    It should check the signed-in user, call POST /v1/shorts with ?wait=0 and your webhook_url, and save job.id from the { job } envelope on a new row in videos. With ?wait=0 reelsdk answers with a 202 right away, so the function returns in well under a second while the short renders.

    supabase/functions/create-short/index.ts
    import { createClient } from "npm:@supabase/supabase-js@2";
    
    const cors = {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
    };
    
    Deno.serve(async (req) => {
      if (req.method === "OPTIONS") return new Response(null, { headers: cors });
    
      const supabase = createClient(Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_ANON_KEY")!, {
        global: { headers: { Authorization: req.headers.get("Authorization") ?? "" } },
      });
      const { data } = await supabase.auth.getUser();
      if (!data.user) return new Response("sign in first", { status: 401, headers: cors });
    
      const { topic } = await req.json();
      const res = await fetch("https://api.reelsdk.com/v1/shorts?wait=0", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${Deno.env.get("REELSDK_KEY")}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          topic,
          style: "cinematic",
          duration_s: 45,
          webhook_url: `${Deno.env.get("SUPABASE_URL")}/functions/v1/reelsdk-webhook`,
        }),
      });
    
      const { job, error } = await res.json();
      if (!res.ok) return Response.json({ error }, { status: res.status, headers: cors });
    
      await supabase.from("videos").insert({ user_id: data.user.id, topic, job_id: job.id, status: job.status });
      return Response.json({ job_id: job.id }, { headers: cors });
    });
  4. Check the webhook function

    JWT verification has to be off for this one function so reelsdk can reach it, which makes the signature check the lock on the door. It reads the raw body, verifies Reel-Signature against your whsec_ secret, rejects timestamps more than 300 s old and writes the mp4 link to the matching row. A 500 makes reelsdk retry, up to 3 attempts.

    supabase/functions/reelsdk-webhook/index.ts
    import { createClient } from "npm:@supabase/supabase-js@2";
    
    const admin = createClient(Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!);
    
    Deno.serve(async (req) => {
      const body = await req.text();
      const signature = req.headers.get("Reel-Signature");
      if (!(await verifyReelSignature(signature, body, Deno.env.get("REELSDK_WEBHOOK_SECRET")!))) {
        return new Response("invalid signature", { status: 401 });
      }
    
      const job = JSON.parse(body);
      const { error } = await admin
        .from("videos")
        .update({ status: job.status, video_url: job.outputs?.video ?? null })
        .eq("job_id", job.id);
    
      // A 500 makes reelsdk retry the delivery
      if (error) return new Response(error.message, { status: 500 });
      return new Response("ok");
    });
    
    async function verifyReelSignature(header: string | null, body: string, secret: string) {
      const parts = Object.fromEntries((header ?? "").split(",").map((part) => part.split("=")));
      const age = Math.abs(Date.now() / 1000 - Number(parts.t));
      if (!/^[0-9a-f]{64}$/i.test(parts.v1 ?? "") || !(age <= 300)) return false;
    
      const encoder = new TextEncoder();
      const key = await crypto.subtle.importKey(
        "raw",
        encoder.encode(secret),
        { name: "HMAC", hash: "SHA-256" },
        false,
        ["verify"],
      );
      const signature = Uint8Array.from(parts.v1.match(/../g), (pair: string) => parseInt(pair, 16));
      return crypto.subtle.verify("HMAC", key, signature, encoder.encode(`${parts.t}.${body}`));
    }

FAQ

Video on Lovable, answered

Why can't I run ffmpeg inside a Lovable edge function?

Lovable Cloud runs edge functions on Supabase's Edge Runtime, which allows 256 MB of memory and 2 s of CPU per request. Starting a binary throws "Spawning subprocesses is not allowed on Supabase Edge Runtime." and heavy memory or CPU work ends in HTTP 546 WORKER_RESOURCE_LIMIT. Send the render to an API and let the function wait or return.

Should I use ffmpeg.wasm in my Lovable frontend instead?

Only for small jobs on desktop. ffmpeg.wasm downloads a core of about 31 MB and encodes on the visitor's own device, and the render stops when the tab closes. If videos have to render reliably on phones, in the background or for many users, do the render on a server.

How long does a short take, and will my edge function time out?

A render machine is ready in about 1.9 s at the median and a faceless short takes about 100 s. Pass ?wait=0 and a webhook_url, as the prompt does, so create-short returns right away and the webhook updates the row when the mp4 is ready. Nothing waits on a wall clock limit.

Can I send my own ffmpeg commands through reelsdk?

Yes. POST /v1/ffmpeg takes your ffmpeg 8.1 arguments, without a shell, for $0.01 per compute minute. The higher-level endpoints do the common jobs in one call: /v1/slideshow and /v1/captions at $0.02 per output minute, /v1/shorts at $0.25 up to 60 s and $0.35 up to 90 s.

How do I get a key, and how does billing work?

Sign up with Google and create a key (rk_live_...) in the dashboard; it comes with a webhook secret (whsec_...). The first $1 is free and there is no subscription or minimum. Each key runs 3 jobs at once and up to 500 jobs a day, and mp4 download links stay up for 7 days.

All integrations

Ready to build?

Ship video from your app today

Your first $1 of renders is free, and one key covers every endpoint.

Moonlit lake with a lit cabin under the mountains