txtbag form


Collect form submissions without maintaining your own backend.


txtbag form gives every form a Formspree + webhook delivery + submission inbox + viewer sharing + export pipeline + API-first security model.


Get started


Limited-time launch offer(Jul 16 - Aug 22, 2026): use code VERYRICH5000 at checkout to get non-expiring 5,000 submission credits free while the quota is available.




One endpoint for every form workflow


Every product needs forms: contact, waitlists, feedback, support, internal ops. The hard part is everything after submit: delivery, privacy, notifications, webhooks, sharing, and exports.


txtbag form brings proper submission workflow.


  • Endpoint for each form.
  • API-key-required mode for trusted backend submissions.
  • Optional browser embeds for no-backend forms.
  • Spam-resistant browser submission flow.
  • Email notifications with burst suppression.
  • Signed outgoing webhooks.
  • Read-only viewer links with optional passwords.
  • XLSX-compatible exports.



How it works


  1. Create a form endpoint.
  2. Choose the submission mode.
  3. Send submissions from your backend, worker, static site, or browser embed.

New forms start in secure server-side mode:


Require API key: on

Use this mode for Next.js route handlers, Flask apps, PHP scripts, Node services, Vercel Serverless Functions, internal tools, and automations.


If you want a pure browser form with no backend, turn off Require API key in form Preferences and use the provided HTML or JavaScript snippets.




Why teams use it


Launch forms without owning the backend


Use a hosted endpoint instead of maintaining custom capture, notification, webhook, and export workflows.


Protect backend submissions by default


Account API keys are designed for trusted server-side code only. Use them for route handlers, workers, automations, and internal tools that need reliable submission access without depending on a browser.


Browser embeds when needed


For marketing sites, static pages, Webflow, WordPress, or simple landing pages, disable Require API key and paste the browser snippet.


Monitoring and observability


Submissions are stored, paginated, exportable, and visible from the dashboard. Delivery records help you see whether notifications and webhooks were sent.


Best effort anti-spam and security set


API-key locking, browser anti-spam checks, rate limits, payload limits, webhook safety checks, and formula-safe XLSX exports are built in.




Features


Feature Included
Hosted form endpoints Yes
API-key-required mode Yes
Server-side API keys Yes
Browser HTML form support Yes
JavaScript SDK Yes
Authorized browser domains Yes
Spam honeypot and timestamp checks Yes
Email notifications Yes
Burst notification suppression Yes
Signed outgoing webhooks Yes
Webhook delivery tracking Yes
Manual webhook resend Yes
Read-only viewer links Yes
Optional viewer password Yes
XLSX export Yes
Formula-safe spreadsheet cells Yes



Common use cases


Contact forms


Collect contact requests from any website. Use the default API-key lock from your backend, or disable it when you want a browser-only embed.


Landing pages


Capture product interest, early access requests, demo requests, and campaign leads without creating a new backend service.


Waitlists


Collect signups over time, review them in the dashboard, and export them when you are ready.


Support requests


Forward submissions to a helpdesk, CRM, Slack bridge, or automation platform through signed webhooks.


Internal tools


Let internal apps submit operational records into a central inbox without provisioning a full data capture service.


Backend automations


Use API keys from Next.js route handlers, Node services, Rails apps, cron jobs, or Cloudflare Workers.




Browser HTML form example


Browser-only HTML forms work when Require API key is disabled for the form. Never place API keys in browser HTML or JavaScript.


Use form-embed.js for the simplest browser integration. It adds bot-check fields, refreshes _ts, captures common tracking parameters, adds an idempotency key, and exposes a small browser API for dynamic apps.


New snippets use the shorter /f/... endpoint. Older /public/f/... endpoints remain available as legacy aliases.


<form action="https://form.txtbag.com/f/form_xxxxxxxxx" method="post" data-formbag>
  <label>
    Email
    <input type="email" name="email" required>
  </label>

  <label>
    Message
    <textarea name="message" required></textarea>
  </label>

  <button type="submit">Send</button>
</form>

<script src="https://form.txtbag.com/form-embed.js" defer></script>

Native browser submissions redirect after success. If your form has no custom redirect URL, the browser lands on https://txtbag.com/form/success.


You can also use data-formbag-id and let the helper set the endpoint:


<form data-formbag data-formbag-id="form_xxxxxxxxx">
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

<script src="https://form.txtbag.com/form-embed.js" defer></script>

Dynamic apps and AJAX forms


form-embed.js exposes a small browser API for forms rendered after page load:


window.formbag.scan(root = document)
window.formbag.prepare(form)
window.formbag.submit(form)

Use scan(root) after rendering a form dynamically, such as after a React route transition, markdown preview render, modal open, or HTMX/Turbo replacement:


useEffect(() => {
  window.formbag?.scan(containerRef.current)
}, [html])

For no-code AJAX, add data-formbag-ajax:


<form action="https://form.txtbag.com/f/form_xxxxxxxxx" method="post" data-formbag data-formbag-ajax>
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

<script src="https://form.txtbag.com/form-embed.js" defer></script>

AJAX submissions stay on the current page. The helper prepares the form with a fresh _ts and idempotency_key, appends ?format=json, sends FormData with Accept: application/json, and dispatches formbag:success or formbag:error.


For custom UI, call submit(form) manually:


const form = document.querySelector("form")

try {
  const result = await window.formbag.submit(form)
  form.reset()
  console.log("sent", result)
} catch (error) {
  console.error("failed", error)
}



Browser JavaScript SDK example


Browser-side SDK submissions work when Require API key is disabled for the form.


<script src="https://form.txtbag.com/formbag.js"></script>

<script>
  const formbag = new Formbag({
    formId: "form_xxxxxxxxx"
  })

  const result = await formbag.submit({
    email: "[email protected]",
    message: "Hello from my site"
  })

  if (!result.success) {
    console.error(result.message)
  }
</script>

The browser SDK automatically adds:


  • Honeypot field.
  • Timestamp field.
  • Idempotency key.
  • Common tracking parameters.



Server-side API example


Use account API keys only from trusted server-side code. This is the default mode for newly created forms.


Fetch, XHR, and SDK submissions receive JSON. Add ?format=json when you want to explicitly opt out of browser redirects.


await fetch("https://form.txtbag.com/f/form_xxxxxxxxx?format=json", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${process.env.FORMBAG_API_KEY}`
  },
  body: JSON.stringify({
    email: "[email protected]",
    message: "Hello from server-side code"
  })
})

Valid API-key submissions can be sent directly from your backend without browser-only anti-spam fields.


You can also submit form-encoded data:


curl -i -X POST "https://form.txtbag.com/f/form_xxxxxxxxx?format=json" \
  -H "Authorization: Bearer $FORMBAG_API_KEY" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "[email protected]" \
  -d "message=Hello from curl"



File attachments


txtbag form stores text fields only. For files, upload to your own storage first, then submit the resulting file URL.


For browser forms, disable Require API key and submit the URL field from the browser. For locked forms, perform both the upload and txtbag form submission from trusted backend code.


Example submitted field:


image_url=https://cdn.example.com/uploads/product-photo.jpg

Your backend can create upload URLs for your storage provider, then send the final file URL to txtbag form.


Example browser-side upload flow:


<script src="https://form.txtbag.com/formbag.js"></script>

<script>
  async function uploadFileWithFetch(file) {
    const presignResponse = await fetch("/presign-upload", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        filename: file.name,
        content_type: file.type
      })
    })

    const presign = await presignResponse.json()

    await fetch(presign.upload_url, {
      method: "PUT",
      headers: { "Content-Type": file.type },
      body: file
    })

    return presign.file_url
  }

  const selectedFile = document.querySelector("input[type='file']")?.files[0]
  const imageUrl = selectedFile ? await uploadFileWithFetch(selectedFile) : null

  const formbag = new Formbag({
    formId: "form_xxxxxxxxx"
  })

  await formbag.submit({
    email: "[email protected]",
    message: "Here is the image URL",
    image_url: imageUrl
  })
</script>



Webhooks


Send every accepted submission to your own endpoint.


Webhook requests include signed headers:


X-TxtbagForm-Event
X-TxtbagForm-Delivery
X-TxtbagForm-Timestamp
X-TxtbagForm-Signature

Example payload:


{
  "event": "submission.created",
  "form": {
    "id": "form_xxxxxxxxx",
    "name": "Contact"
  },
  "submission": {
    "id": "sub_xxxxxxxxx",
    "created_at": "2026-07-09T12:00:00Z",
    "spam": false
  },
  "fields": {
    "email": "[email protected]",
    "message": "Hello"
  }
}

Webhook deliveries are queued, retried, and tracked. You can manually resend a webhook from the submission detail page.




Viewer links


Share a read-only view of submissions without giving dashboard access.


Viewer links can be:


  • Enabled or disabled.
  • Rotated.
  • Protected with a password.
  • Invalidated by changing the password.
  • Logged out manually from the viewer page.
  • Used for external monitoring.

Password-protected viewer sessions expire automatically after 12 hours.




Exports


Download submissions as .xlsx.


Exports include:


  • Submission ID.
  • Submitted timestamp in UTC.
  • Submitted local timestamp.
  • Submitted timezone.
  • Submitted fields.
  • Formula-injection-safe cell values.

Exports are generated asynchronously and downloaded through authenticated, short-lived links.


Stored submissions are retained only for 90 days so ensure you export them in a timely manner because we don't store them indefinitely. Export files expire after 24 hours.




Pricing


Pack Best for Included Price
Free Small forms and early projects 500 free submissions per month, dashboard, viewer links, webhooks, exports Free
5,000 credits Growing sites One-time/ overage submission credits used after free quota $5
10,000 credits Higher-volume forms One-time/ overage submission credits used after free quota $10

*Contact us for usage beyond 100,000 credits.


No subscription is required.


Referral credits are promotional, non-transferable, and have no cash value.




Security and privacy


  • Public form IDs are random and prefixed.
  • New forms require API keys by default.
  • Submitted payloads are encrypted at rest.
  • Stored submissions are retained for 90 days.
  • Export files expire after 24 hours.
  • Logs and error reporting scrub submitted fields and sensitive details.
  • API keys, viewer passwords, and webhook secrets are handled as sensitive credentials.
  • Webhook destinations are checked before delivery.
  • Export downloads use short-lived signed links.
  • Public pages and private dashboard pages are noindex.



Start accepting submissions


Create a form endpoint, choose how submissions arrive, and start collecting data without maintaining your own form backend.


Invite a teammate or friend and both accounts get 200 submission credits after the referred account qualifies. Referral credits are promotional, non-transferable, subject to abuse checks, and have no cash value.


Get started | Log in