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.
The form server
Section titled “The form server”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.
Set it up
Section titled “Set it up”- Remix this template — click the Remix button in the top right corner.
- Open the HTTP endpoint of
main.tsx(shown on its HTTP trigger) — that's your live form. Share the URL. - Visit
/submissionson 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.
/submissionsis 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 inmain.tsx, and the table indb.ts.
Next steps
Section titled “Next steps”- SQLite reference — querying and migrating your submissions table
- Save HTML form data — the same pattern built up from scratch
- HTTP val reference — routing, JSX, and custom domains