Reference · Cookies and sessions
set_cookie
set_cookie(str, any=, map | null=) -> nullWhat it does
Adds a Set-Cookie header to the request's answer, whatever the route returns, from
anywhere in the request: a route, a server fn, a helper. The value is percent-encoded
on the way out and decoded by cookie, so any text fits; a value that is not a string
is written as text, and none as the empty string. The options are max_age, path,
domain, http_only, secure and same_site, and another is an error; without them a
cookie is HttpOnly, Secure, SameSite=Lax, for Path=/, and lasts until the
browser closes. It gives null. The guide Headers, cookies and
sessions has the options' table.
Examples
A page whose buttons post a choice, the route that keeps it in a cookie, and the page
reading it back with cookie on the next visit. The button's name and value are
the form's field:
route GET / { let theme = cookie("theme") ?? "light" <main class={theme}> <form method="post" action="/theme"> <button name="theme" value="light">Light</button> <button name="theme" value="dark">Dark</button> </form> </main>}route POST /theme { form: { theme: str } } { if theme != "light" && theme != "dark" { reply 400 "No such theme" } // a form can send anything set_cookie("theme", theme, { max_age: "1y" }) redirect("/")}route POST /tz { form: { tz: str } } { set_cookie("tz", tz, { max_age: "1y", http_only: false }) // the page's script may read it { ok: true }}Outside a request there is no answer to carry the cookie; the call still checks its arguments and gives null:
print(set_cookie("theme", "dark", { max_age: "30d" }))print(cookie("theme"))nullnullEdge cases
max_ageis seconds or a duration:"30d"withs,m,h,d,w,y. Without it the cookie is a session cookie, gone when the browser closes.same_site: "None"makes the cookieSecurewhateversecuresays: browsers take it no other way.Secureis left out with thequillo runoptions--watchand--plain-http(quillo run main.qwe --watch; see the options) and in desktop apps, where the site is served over plain HTTP.- A cookie replaces another set in the same request only at the same name, path and
domain (a domain in any case, with or without its leading
.), as browsers keep them:set_cookie("t", "1", { path: "/a" })andset_cookie("t", "2", { path: "/b" })send both. - Inside
parallel's functions it works as anywhere in the request: their cookies go out with the answer, taken in the functions' order (they were dropped). - An option it does not take is an error, when the program runs and in
quillo checkfor one written out. Express's and the header's spellings (sameSite,maxAge,httpOnly,Max-Age) are told the name here;expiresis told thatmax_ageis how long it lasts. - A name starting
__Host-or__Secure-, in any case, keeps its prefix's rules, or browsers drop the cookie: it isSecure, under--watchand--plain-httptoo (browsers take it fromlocalhost), and a__Host-one is atPath=/with noDomain. Asking otherwise is an error. - A cookie of more than 3,900 bytes as sent, its value percent-encoded, is an error: browsers drop it with its attributes.
- A
pathholds no space,;, control character or letter past ASCII: percent-encode them. - A name is letters, digits and
-_.!#$%&'*+^`|~; a value that is not a string is written as text (42), and a missing or null value as the empty string. - Under
--base(quillo run main.qwe --base /shop) or in a mounted program, the defaultpathis that prefix. - Keep secrets out: the browser, and anything on the machine, can read a cookie. For "remember me", store a random token in the database and put only the token in the cookie.
print(set_cookie("n", 42), set_cookie("n"), set_cookie("n", null))print(set_cookie("__Host-cart", "7", { max_age: "7d" }), set_cookie("a", "b", { same_site: "None", secure: false }))null null nullnull nullErrors
A bad cookie name, a max_age that is not seconds or a duration, a path not starting
with / or holding a space, a domain that is not a host name, a same_site other
than "Lax", "Strict" or "None", an option it does not take, a prefixed cookie
that breaks its prefix's rules, a cookie of more than 3,900 bytes, and options that are
not a map are runtime errors, not error values.
set_cookie("bad name", "x")line 1: `set_cookie` needs a cookie name of letters, digits and -_.!#$%&'*+^`|~, got "bad name"set_cookie("a", "b", { same_site: "nope" })line 1: `set_cookie`'s same_site is "Lax", "Strict" or "None", got "nope"set_cookie("theme", "dark", { sameSite: "Strict" })line 1: `set_cookie` has no option `sameSite`: write it `same_site`set_cookie("__Host-cart", "7", { domain: "example.com" })line 1: `set_cookie`: a `__Host-` cookie has no domain, as browsers take it only from the host that sets it; leave `domain` outChaining
set_cookie is a statement with a side effect, so it sits on its own line before the
route's answer rather than in a pipeline. A helper can set a cookie and give its value
back for the rest of the route:
fn remember_theme(theme: str) { set_cookie("theme", theme, { max_age: "1y" }); theme }route POST /theme { form: { theme: str } } = <Page theme={remember_theme(theme)}><Home/></Page>See also
cookie, delete_cookie, set_session, respond
Thread 0 posts
On set_cookie(): questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.
Edit this page on GitHub · every example's output was made by running it (tools/refcheck.mjs) · questions on Discord or r/quillo