Caddi
Sign inSign up

Forms

A real form backend, in every site you ship.

Per-site endpoints, presigned file uploads, spam protection, webhooks, and a triage inbox you'll actually use. No more emails-from-an-EC2-script.

The Caddi submissions inbox showing seven recent submissions across four client sites.

What's included

The whole pipeline, owned by you.

  • Per-site endpoint

    Every project gets a unique URL — no shared keys, no leaked secrets in client-side code. Routes by site, not by account.

  • Presigned uploads

    Files go straight from the browser to your R2 bucket via short-lived signed URLs. Receipts, photos, contracts — up to 25MB each.

  • Spam protection

    Rate limiting, honeypots, and a configurable hCaptcha hook. Nothing reaches the inbox unless it passes.

  • Submission inbox

    A real triage UI in the agency dashboard — filter by site, status, or form. Reply, archive, mark as spam, export to CSV.

  • Webhooks and email

    Forward each submission to Slack, Discord, Linear, or any URL. Send the customer an autoresponder via Resend, with templating.

  • Idempotency built-in

    Every POST is keyed. Double-submits from a flaky network become a single row, never two.

Drop-in

Two lines in any frontend.

Install the SDK and call submit(). The endpoint URL is per-site and shipped as an env var.

ts · React / Astro / Next
import { submit } from '@caddi/forms-sdk';

export async function ContactForm({ values }) {
  const res = await submit({
    endpoint: import.meta.env.PUBLIC_CADDI_FORM_URL,
    fields: values,
    files: values.attachments, // optional — uses presigned R2 uploads
  });

  if (res.ok) {
    // res.id is your submission ID for follow-up.
    return { redirect: '/thanks' };
  }
  return { errors: res.errors };
}

API

Or just POST to the endpoint.

The SDK is sugar around a JSON endpoint. Use whatever you’d like.

bash
curl -X POST https://forms.caddi.build/f/fm_01H9X4K2P3JR \
  -H "content-type: application/json" \
  -H "idempotency-key: $(uuidgen)" \
  -d '{"name":"Alicia","email":"[email protected]","message":"hi"}'

FAQ

Frequently asked.

Do I need a database for forms?
No. Submissions are stored in Caddi with row-level security per agency, and exposed to your dashboard and the client portal. You can still forward them to your own DB via webhook.
Can clients see their own submissions?
Yes. The branded customer portal includes a submissions tab scoped to that client only — they never see another client’s data.
What does the SDK do?
@caddi/forms-sdk is a tiny helper (~1.5kB gzipped). It handles the POST, surfaces validation errors, and abstracts the presigned-upload step for files. Use it from React, Astro, vanilla JS — anything.
How does spam protection work?
Rate-limits per IP and per endpoint, a honeypot field rejected silently, and a checkbox-style hCaptcha challenge that you can enable per-form. Suspicious submissions land in a Spam queue, not the main inbox.

Stop hand-rolling form endpoints.

Your one Caddi plan gives every site a real form backend, an inbox, and uploads. Set up takes about three minutes.

Get started