For Website Developers

Form Submissions

Collect form submissions from your static website

Form Submissions

The platform provides a built-in form submission endpoint so you can collect data from your static website without a third-party service. Submissions are stored in the dashboard and can optionally trigger webhooks.

How It Works

  1. Your HTML form POSTs to /app/forms/submit
  2. The platform parses the data and redirects to a bot-protection challenge (Cap.js)
  3. After verification, the submission is stored and the user sees a thank-you page
  4. Website owners receive an email notification (sandbox submissions are excluded)

Basic Form

Create a standard HTML form with action="/app/forms/submit" and method="POST":

<form action="/app/forms/submit" method="POST">
  <label for="full_name">Full Name</label>
  <input type="text" id="full_name" name="full_name" required>

  <label for="email">Email</label>
  <input type="email" id="email" name="email" required>

  <label for="message">Message</label>
  <textarea id="message" name="message"></textarea>

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

Each form field's name attribute becomes a column in the submissions table. Use descriptive names — they're displayed as column headers in the dashboard.

Config Fields

Fields with names starting with _ are treated as config (not displayed as submitted data). Use hidden inputs to control form behavior:

<input type="hidden" name="_name" value="contact">
<input type="hidden" name="_return_url" value="https://example.com/thank-you">

Available config fields:

  • _name — Identifies the form. Submissions are grouped by name in the dashboard. Required if using the registration workflow.
  • _return_url — Where the "Return" button on the thank-you page links to. Defaults to /.
  • _sandbox — Set to any value to mark the submission as a test. Sandbox submissions are filtered separately in the dashboard.

Field Name Ordering

Field names can include a numeric sorting prefix separated by a hyphen. The prefix is stripped before storage:

<!-- Stored as "full_name", "email", "message" — but rendered in this order -->
<input name="01-full_name" ...>
<input name="02-email" ...>
<input name="03-message" ...>

This is useful when you want to control column order in the dashboard table.

Multi-Value Fields

If a field name appears multiple times (e.g., checkboxes), all values are stored as an array:

<input type="checkbox" name="interests" value="Sports"> Sports
<input type="checkbox" name="interests" value="Music"> Music
<input type="checkbox" name="interests" value="Art"> Art

Single-value fields are stored as plain strings.

File Attachments

Forms can collect file uploads — resumes, photos, signed PDFs, screenshots. There are two steps: load the r2ware site script, then add a <input type="file"> to a form marked with data-controller="form-uploads".

1. Load the r2ware site script

Add this once per page that has an upload form (just before </body>):

<script src="https://cdn.r2ware.dev/dash/static/v0.1.1/r2ware.min.js"></script>

This is the same script that powers the shop and other r2ware features, so branded r2ware sites already include it — check your layout before adding a second copy. Use the latest version tag (here v0.1.1).

2. Add the file input

Add a <input type="file"> and put data-controller="form-uploads" on the <form>:

<form action="/app/forms/submit" method="POST" data-controller="form-uploads">
  <label for="name">Name</label>
  <input type="text" name="name" required>

  <label for="resume">Resume (PDF)</label>
  <input type="file" id="resume" name="resume" accept="application/pdf">

  <!-- optional: upload errors are shown here -->
  <div data-form-uploads-target="error" hidden></div>

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

How it works

When the form is submitted, each selected file uploads directly to storage from the browser — the bytes never pass through the form endpoint. Only a reference to each file is saved with the submission. In the dashboard and in the notification email, attachments appear as download links (files are private — you must be signed in to open them).

Use multiple to accept several files in one field:

<input type="file" name="photos" accept="image/*" multiple>

Accepted types and limits

  • Allowed types: images (JPEG, PNG, GIF, WebP, HEIC), PDF, plain text, CSV, MP4, QuickTime (MOV), MP3, WAV, and ZIP.
  • Per file: up to 10 MB.
  • Per submission: up to 25 MB across all files.

Files outside these limits are rejected before the form is sent, and the visitor sees an error.

Requirements

File upload needs JavaScript (and the script from step 1). Without it, the file input is ignored and the rest of the form still submits normally. Text-only forms work with or without JavaScript.

Sandbox / Test Detection

Submissions are automatically marked as sandbox (test) if any of these are true:

  • The query string includes ?sandbox=on
  • The _sandbox config field is set
  • Any field value contains the string testtesttest

Sandbox submissions appear in the dashboard but are filtered into the "Sandbox" environment. They do not trigger notification emails.

Bot Protection

All submissions pass through a Cap.js challenge before being stored. This is handled automatically — no additional markup is needed in your form. The challenge page preserves your original form data and resubmits it after verification.

Spam Protection

On top of the Cap.js challenge, submissions are scanned for spam in two layers. Both layers set a spam flag and score on the submission; flagged submissions are still stored but are marked in the dashboard and don't trigger normal notification emails.

Honeypot Fields

Add a hidden honeypot field to any form using the _hp_ prefix. The field is invisible to humans but gets filled in by bots — any submission with a non-empty _hp_* field is immediately flagged as spam (no LLM scan needed).

<form action="/app/forms/submit" method="POST">
  <!-- Real form fields -->
  <input type="text" name="name" placeholder="Your name">
  <input type="email" name="email" placeholder="Email">
  <textarea name="message" placeholder="Message"></textarea>

  <!-- Honeypot: hidden from humans, filled by bots -->
  <div style="display:none" aria-hidden="true">
    <label for="_hp_website">Leave blank</label>
    <input type="text" name="_hp_website" tabindex="-1" autocomplete="off">
  </div>

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

Rules:

  • Field names must start with _hp_ (e.g. _hp_website, _hp_company, _hp_fax)
  • Hide the field with style="display:none" — not type="hidden", which bots can detect
  • Add tabindex="-1" and aria-hidden="true" so the field is skipped by keyboard and screen-reader users
  • Add autocomplete="off" so browsers don't auto-fill it
  • Multiple honeypot fields are supported — any non-empty one triggers the flag
  • Honeypot field names and values are never stored in the submission data

LLM Classifier

Submissions that pass the honeypot check are scanned by an LLM-based classifier in a background task. It reads the submission's text fields and assigns a spam confidence score from 0.0 to 1.0; submissions scoring at or above the spam threshold are flagged.

This runs automatically with no markup or configuration on your end. The scan happens exactly once per submission, and the resulting flag and score appear on the submission detail in the dashboard.

Notification Emails

Non-sandbox submissions queue an email notification to the website's contact address (configured in _config.yml as contact, or the platform default). Notifications are batched and sent every 3 hours, grouped by website.

Complete Example

<form action="/app/forms/submit" method="POST">
  <input type="hidden" name="_name" value="contact">
  <input type="hidden" name="_return_url" value="/thank-you/">

  <div>
    <label for="full_name">Full Name</label>
    <input type="text" id="full_name" name="01-full_name" required>
  </div>

  <div>
    <label for="email">Email Address</label>
    <input type="email" id="email" name="02-email" required>
  </div>

  <div>
    <label for="phone">Phone</label>
    <input type="tel" id="phone" name="03-phone">
  </div>

  <div>
    <label for="message">Message</label>
    <textarea id="message" name="04-message" rows="4"></textarea>
  </div>

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

Testing

During development, add ?sandbox=on to the form's action URL or include testtesttest in a field value to avoid triggering notification emails:

<form action="/app/forms/submit?sandbox=on" method="POST">
  ...
</form>