Built out of spite. Made for control.
An open-source, node-based visual canvas for AI filmmaking. Bring your own API keys. Pay providers directly. Own your workflow.
SPITE was built because every AI filmmaking tool either traps you in a credit system you can't audit, or assumes you're an ML engineer who enjoys wiring up diffusion models by hand. This is neither. It's a production canvas for people who think in shots and scenes — characters, prompts, references, generated images, generated video, all on one infinite plane, all owned by you.
- Node canvas. Drag prompts, references, image generators, and video generators onto an infinite plane. Connect them. Hit Generate on any node and SPITE orchestrates the right model call.
- Character consistency. Tag images to a named folder
(Character / Prop / Location), use
@FolderNamein any prompt, and SPITE wires the reference into every generation that mentions it. - Multi-model. Nano Banana Pro, FLUX, Kling, Seedance, Luma Ray2, MiniMax Hailuo, Wan — all in one canvas, switchable per node, all routed through fal.ai with your own key.
- Scenes and shots. First-class production primitives. Tag a node as Shot 1 of Scene A; the scene strip at the top of the canvas keeps the structure visible.
- Asset library. Every generation persists, organised by date, model, and project. Protected from cleanup if used on a canvas.
- Generation recovery. When fal.ai stalls or the page reloads mid-generation, the recovery system pulls completed jobs back from fal within their 24h retention window. No other tool does this.
- Cost-aware UX. Estimated cost on every Generate button. Live fal.ai
balance badge.
$25confirmation threshold. Staggered batch submission. All built after a real $200-in-24-hours incident. - Export. Storyboard zip with one folder per scene, files named by shot order. Drop it straight into Premiere or Resolve.
- Snapshots. Canvas state saves every three seconds. Point-in-time snapshots kept separately for recovery.
- Auth. Single-user password gate. The internet does not need to see your work.
- No video editing — SPITE is pre-production.
- No real-time multi-user collaboration — single-user by design.
- No automation that makes creative decisions for you.
- No credit system, no markup, no subscription.
- Node 20+ and pnpm (or npm/yarn — pnpm is what we develop against)
- A free Neon account (Postgres)
- A Cloudflare account with R2 enabled (S3-compatible storage; free tier covers most personal use)
- A fal.ai account with a funded key (the generation provider — you pay them directly per generation)
git clone <your-fork-url>
cd spite
pnpm installCopy the template and fill in real values:
cp .env.example .env.localEvery field is required except the ones marked optional. SPITE refuses to
boot with missing variables — it will route every request to a /setup
page listing what's missing, with hints. You won't be guessing.
Where to find each value:
| Variable | Source |
|---|---|
DATABASE_URL |
neon.tech → your project → Connection string |
R2_ACCOUNT_ID |
Cloudflare dashboard → R2 (right sidebar) |
R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY |
Cloudflare R2 → Manage API tokens → Create API token |
R2_BUCKET_NAME |
Whatever you named the bucket you created |
FAL_KEY |
fal.ai → Dashboard → Keys |
APP_PASSWORD |
You pick — strong, your responsibility |
Open your Neon project's SQL editor and paste the contents of
database-setup.sql. Hit Run. The script is
idempotent — re-running it is safe and won't touch existing data.
The browser uploads media files directly to R2 via a presigned URL. R2
ships CORS-locked — without a CORS rule the browser's PUT dies with
Failed to fetch, and assets never reach the bucket. Set this once
in the Cloudflare dashboard:
- Cloudflare → R2 → click your bucket
- Settings tab → CORS Policy → Edit CORS policy
- Paste this and save — replace the placeholder with YOUR deployment's exact origin(s):
[
{
"AllowedOrigins": ["https://your-app.vercel.app", "http://localhost:3000"],
"AllowedMethods": ["PUT", "GET", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]Pin AllowedOrigins to the precise origins you actually deploy from (your
production domain and http://localhost:3000 for local dev). Do not use a
broad wildcard like https://*.vercel.app — that would let a page on any
Vercel-hosted site drive a browser request against your bucket if it ever
gets hold of a presigned URL. If you use a custom domain, list that instead,
e.g. "https://your-app.example.com". If you genuinely need preview deploys
to upload, add each specific preview origin rather than the whole *.vercel.app
space.
pnpm devOpen http://localhost:3000, type the APP_PASSWORD you set, and you're
in.
- Push your fork to GitHub.
- Import the repo on vercel.com/new.
- Vercel → Project → Settings → Environment Variables. Add every name
from
.env.examplewith its real value. (Don't commit.env.localto git —.gitignoreshould already block it. The includedscripts/check-no-secrets.shwill catch you if you try.) - Trigger a deploy. The first deploy will route to
/setupif anything is missing.
There's a scheduled cleanup endpoint at /api/assets/cleanup that deletes
old, unprotected generated assets after 30 days and sweeps the auth / spend
bookkeeping tables so they don't grow unbounded. A daily schedule is already
defined in vercel.json (04:00 UTC). To activate it, just
set CRON_SECRET to a long random string in your Vercel env — Vercel
automatically sends it as Authorization: Bearer <CRON_SECRET> on each cron
run, and the endpoint refuses to do anything without a matching secret.
If CRON_SECRET is unset the cron simply no-ops (returns 500 and logs), so
nothing breaks — but the cleanup won't run. On non-Vercel hosts, point any
external scheduler (GitHub Actions, etc.) at the same path with that
Authorization header; both GET and POST work.
app/ Next.js App Router pages + API routes
api/ Server-side endpoints (auth, generate, assets, R2 proxy)
login/ The password gate
setup/ Shown when required env vars are missing
project/[id]/ The canvas for one project
components/
canvas/ The node-based workspace (nodes, edges, toolbars)
ui/ shadcn/ui primitives
lib/
env-check.ts Refuse-to-boot env validation
r2-upload.ts R2 SDK wrapper + HMAC URL signing for the proxy
fal-models.ts Per-model config: endpoint, input shape, cost
fal-cost.ts Cost estimation + confirmation threshold
mention-prompt.ts Model-aware reference grammar compilation
scripts/ Tooling (secret check, etc.)
database-setup.sql Idempotent schema for first-time setup
middleware.ts Auth gate + env-check redirect
Contributions are welcome — especially from people using AI tools in
real production workflows. See CONTRIBUTING.md for
how, what's useful, and what isn't.
If something is broken, file an issue with reproduction steps. If you added a feature, explain what production problem it solves, not just what it does.
The bar isn't perfection. It's honesty and usefulness.
SPITE is provided as-is, with no warranty of any kind (see
LICENSE for the legal version — AGPL §15 and §16).
You're responsible for:
- Your fal.ai bill. The cost gates (per-button estimates, $25
confirm dialog, live balance badge, server-side per-hour ceiling,
kill switch) are best-effort. A bug, a misconfiguration, or a
bypass we haven't anticipated could still result in unexpected
charges. The hourly ceiling defaults to $100 and can be tuned via
SPEND_LIMIT_USD_PER_HOUR. - The content you generate. SPITE doesn't filter prompts or outputs. Whatever your chosen models do, you've made.
- Your data. Your assets live in your R2 bucket; your projects and metadata live in your Neon database. Nobody else has access. If SPITE breaks, the worst case is you redeploy and possibly drop a couple of tables. There's no central service that can lose your work — it's already in your storage.
If you find a security issue, report it via GitHub Security
Advisories,
not a public issue. See CONTRIBUTING.md for
the rest of the disclosure flow.
AGPL-3.0-only. If you fork SPITE and run it as a service, your modified source must be available to your users. Self-hosting for yourself or your team has no such obligation.
This license exists because the whole point of SPITE is that creative infrastructure shouldn't disappear behind closed doors. Forking SPITE and making it closed-source would defeat the project's reason for existing.
SPITE is not finished. That is the point. But it should always be getting more useful, not more complicated.