Skip to content

Put a form online and save submissions

The easiest way to put a form online is to remix a val that already is one. This template serves a contact form (name, email, message) at /, saves every submission to the val's own SQLite database, and lists everything received at /submissions. No form-builder service, no API keys, no environment variables.

View and run this example on Val Town

/** @jsxImportSource npm:hono/jsx */
import { parseVal } from "https://esm.town/v/std/utils/index.ts";
import { Hono } from "npm:hono";
import FormPage from "./components/FormPage.tsx";
import SubmissionsPage from "./components/SubmissionsPage.tsx";
import { addSubmission, listSubmissions } from "./db.ts";
const app = new Hono();
app.get("/", (c) => c.html(<FormPage />));
app.post("/submit", async (c) => {
const form = await c.req.formData();
const name = String(form.get("name") ?? "").trim();
const email = String(form.get("email") ?? "").trim();
const message = String(form.get("message") ?? "").trim();
if (!name || !email || !message) {
return c.text("Name, email, and message are all required.", 400);
}
await addSubmission(name, email, message);
return c.redirect("/submissions");
});
app.get("/submissions", async (c) => {
const submissions = await listSubmissions();
return c.html(<SubmissionsPage submissions={submissions} />);
});
app.get("/source", (c) => c.redirect(parseVal().links.self.val));
app.onError((err) => Promise.reject(err));
export default app.fetch;

db.ts creates the submissions table and holds the two queries, and the components/ files render the pages with Hono JSX. The form is a plain HTML POST — there's no client-side JavaScript at all.

  1. Remix this template — click the Remix button in the top right corner.
  2. Open the HTTP endpoint of main.tsx (shown on its HTTP trigger) — that's your live form. Share the URL.
  3. Visit /submissions on the same URL to see what people sent.
  • Each remix gets its own private SQLite database, so your submissions stay with your copy. You can also browse them in the val's SQLite tab.
  • /submissions is public by default. If your form collects anything sensitive, protect that route — the auth guide shows how to add basic or password auth — before sharing the form URL.
  • To change the fields, edit the form in components/FormPage.tsx, the validation in main.tsx, and the table in db.ts.