Skip to article
HANDBOOK / CREATE A WEBSITE

Server-side Lua

Handle a request, build a page, and return a response. Start with a complete working file.

FORMAT 1Updated September 19, 2026
On this page

The handler contract

The file must return a function. Lantern calls that function with ctx for each request. The function returns page data, a redirect, an error, or nil for static fallback.

local P = ctx.page is simply a convenient abbreviation. P.document adds the document version and title; P.paragraph, P.heading and other builders add the component type. They do not print or draw directly.

app.lua
return function(ctx)
  local P = ctx.page
  return {kind="page", page=P.document("My first site", {
    P.heading{text="Hello, other worlds!"},
    P.paragraph{text="testing"},
    P.link{text="Explore the Hub", href="hub://directory/"}
  })}
end

Request context

MemberMeaning
ctx.methodGET for navigation; POST for forms
ctx.pathRooted site path, such as / or /greet
ctx.fieldsSubmitted string or boolean field values
ctx.requestIdRequest identifier, not a secret or user identity
ctx.page.document(title, children)Build a version-1 page
ctx.page.<component>{...}Build any documented component
ctx.read(key, default)Read persistent site data
ctx.write(key, value)Write JSON-compatible site data on POST

Response types

Alternative return statements
-- Render a document
return {kind="page", page=P.document("Title", {P.paragraph{text="Hello"}})}

-- Navigate within this site
return {kind="redirect", path="/"}

-- Report an input or route error
return {kind="error", status=400, message="Please enter a name"}

-- Fall back to a static JSON file on GET
return nil

Route before doing work

Test both ctx.method and ctx.path before handling a form. Return nil for static GET fallback, or an explicit 404 error for unknown routes. Do not change data merely because someone opens a page.

Available cloud Lua functions

The environment exposes assert, error, ipairs, pairs, next, pcall, select, tonumber, tostring and type, plus math, string, table and utf8. It does not expose print, os, io, shell, fs, http, peripheral, require, package, load or debug. There is no arbitrary network access.

Every cloud request starts fresh. Variables and globals from an earlier request are gone. Local hosts use the installed computer’s Lua environment and privileges, so local success does not prove cloud compatibility. Use only the shared ctx API for portable handlers.

Found a mismatch? Include the exact error, runtime version and a small example with secrets removed.