Forms and masked input
Collect input, validate it on the server, and return a useful response.
On this page
A complete greeting form
On GET /, the handler returns a form. Activating its submit control sends POST /greet with ctx.fields.name. The handler validates the field and returns a personalized page. Paste the whole example into app.lua, then validate and preview.
return function(ctx)
local P = ctx.page
if ctx.method == "POST" and ctx.path == "/greet" then
local name = ctx.fields.name
if type(name) ~= "string" or not name:match("%S") or #name > 60 then
return {kind="error", status=400, message="Enter a name, up to 60 characters."}
end
return {kind="page", page=P.document("Welcome", {
P.heading{text="Hello, " .. name .. "!"},
P.link{text="Try again", href="/"}
})}
end
if ctx.method ~= "GET" or ctx.path ~= "/" then
return {kind="error", status=404, message="Page not found"}
end
return {kind="page", page=P.document("Say hello", {
P.form{action="/greet", children={
P.input{name="name", text="Your name"},
P.submit{text="Say hello"}
}}
})}
endField behavior
Give each field in a form a distinct name using letters, digits, underscores or hyphens (up to 40 characters). Put all field controls inside form.children. Nested forms are invalid. A form can submit only to a path on its own site.
Use Tab and Enter, or click a control. Input opens a text prompt, checkboxes toggle, and select controls choose an option. Navigating clears form values. Always validate incoming values: another client can submit fields without using your form.
| Control | Submitted value | Initial value |
|---|---|---|
| input | String | Empty string |
| checkbox | Boolean | false |
| select | Selected string | First option |
| submit | No named value | Not applicable |
Password masking is not authentication
password=true hides typing and the displayed value in an updated Lantern browser. The site operator still receives the field. Masking does not create users, sessions, password hashing, authorization or a password-reset flow. Never ask visitors to reuse real passwords in a demo.
The official Observatory uses the public code glow. It checks that code and returns a response without saving or echoing it. This demonstrates conditional server logic, not a private account system.
P.input{name="code", text="Demo code", password=true}Retries and side effects
The Hub records request IDs to prevent silent repeated execution. Reusing an ID with different content is rejected. A request that is still running or has failed is not automatically rerun. If a submission fails, check the current state before asking someone to submit it again.
Do not use ctx.requestId as proof of identity. Requests do not include a built-in authenticated site visitor. The Hub creator’s GitHub session is separate from visitors to your Lantern pages.
Found a mismatch? Include the exact error, runtime version and a small example with secrets removed.