DeveloperArchitecture

Architecture. How Plooi folds.

A high-level view of the moving parts — the browser, Supabase, and the AI providers that sit behind each node run.

System overview

Browser (React/Vite)
  ├─ Canvas UI & node graph
  ├─ Auth & realtime (Supabase)
  ├─ Billing UI (Stripe)
  └─ Analytics (PostHog)

Supabase (EU / Frankfurt)
  ├─ Postgres + RLS
  ├─ Realtime (presence, collaboration)
  └─ Edge functions (AI, billing, email, BYOK)

AI providers (BYOK)
  ├─ Anthropic Claude (text, reasoning)
  ├─ OpenAI GPT-4o (text, vision)
  └─ Google Gemini + Imagen + Veo (text, image, video)

Core building blocks

  • Node graph as the central data model — nodes, connections, groups.
  • Service layer (TypeScript) wraps every call to Supabase, Stripe, and AI providers.
  • Edge functions carry the server-side logic — credits, webhooks, BYOK keys, deletion flows.
  • RLS enforces row-level access on every table. auth.uid() = user_id is the rule almost everywhere.

Data flow (AI run)

  1. User triggers a node run in the canvas.
  2. Frontend calls the ai-proxy edge function with the user’s auth token.
  3. The edge function checks credits, pulls the user’s encrypted provider key from user_api_keys, decrypts it server-side via HKDF-SHA256 + AES-256-GCM, and forwards the request to the provider.
  4. Result is written to the project and rendered back into the canvas. Plaintext key is discarded after the call. Usage is logged to the credit ledger.

BYOK at the architecture layer

Provider keys are encrypted with AES-256-GCM, with a per-row 16-byte salt and 12-byte nonce. The encryption key is derived per-row via HKDF-SHA256 from a master key kept in Supabase Function secrets — a separate store from Postgres.

  • Writes go through the store-api-key edge function. The browser never writes ciphertext directly.
  • Reads from the browser hit the user_api_keys_public view, which exposes only id, service_name, key_hint, and key_version. The ciphertext column is revoked from the authenticated role.
  • Decrypts happen only inside ai-proxy, in memory, for one call at a time. Plaintext never lands in logs, audit trails, Sentry, or backups.

See Bring your own key for the full picture.

Detailed module maps, table layouts, and service references live in ARCHITECTURE.md and docs/API_REFERENCE.md in the repo.