Ctx and Response
On this page:
- spider.Ctx: The request, and what builds the response: every handler receives a
*Ctx. - spider.NextFn: What a middleware calls to let the request go on.
- spider.MiddlewareFn: A middleware:
fn (*spider.Ctx, spider.NextFn) !spider.Response. - spider.ErrorHandler: The function given to
server.onError. - spider.Response: What a handler returns; built by
c.json,c.view,c.redirectand the like. - spider.DownloadOptions: Options of
c.download(bytes, .{ .filename = …, .content_type = … }). - spider.content_types: Content types for downloads:
.xlsx,.csv,.pdf,.binary. - spider.statusForError: The HTTP status an error gets by default:
error.NotFoundis 404, and so on.
spider.Ctx
pub const Ctx = struct
The request, and what builds the response: every handler and middleware receives a *Ctx.
fn show(c: *spider.Ctx) !spider.Response {
const id = c.params.get("id") orelse return error.NotFound;
return c.json(.{ .id = id }, .{});
}
Fields whose name starts with _ belong to Spider: they change without notice.
Fields
request: std.http.Server.Request: The request as std parsed it:request.head.method,request.head.target. Most handlers usegetMethod,getPath,headerandqueryinstead.arena: std.mem.Allocator: Memory for this request, freed when the response is sent. What a handler allocates for its answer goes here; nothing is freed by hand.params: std.StringHashMapUnmanaged([]const u8): The:namesegments of the matched route:c.params.get("id"). A value is the segment as it came in the URL: not percent-decoded. Spider also keeps who the request is from here, under names that start with_auth_; read those withuserId,hasRoleand the like.body: ?[]const u8 = null: The request body, whole, or null when there is none. A body sent with chunked transfer encoding arrives here put together, like one with aContent-Length. SeebodyJsonandparseForm.
Ctx.requestId
pub fn requestId(self: *Ctx) []const u8
Id correlating every log line and the response of this request: the incoming X-Request-Id when it looks sane (e.g. set by a proxy: 1 to 64 letters, digits, -, _ or .), otherwise 16 random hex digits. Also sent back as the X-Request-Id header.
Ctx.setErrorDetail
pub fn setErrorDetail(self: *Ctx, detail: []const u8) void
Attach a human-readable reason to the error about to be returned (e.g. "invalid path param: id"); onError/default handling can show it. detail is not copied: a literal, or memory from c.arena.
Ctx.errorDetail
pub fn errorDetail(self: *Ctx) ?[]const u8
The reason attached with setErrorDetail, if any.
Ctx.wantsJson
pub fn wantsJson(self: *Ctx) bool
True when the client expects JSON rather than HTML: an explicit Accept: application/json, or a script request (fetch/XHR: Sec-Fetch-Dest: empty) that isn't htmx. Lets an error handler answer fetch() callers with a parseable body instead of an HTML page.
Ctx.io
pub fn io(self: *const Ctx) std.Io
The std.Io the server runs on: files, sockets, sleep and locks take it. Use this one inside a request, never an Io of your own: under the zio backend another Io does not work on the server's sockets.
const res = try spider.http_client.get(c.io(), c.arena, url, .{});
Ctx.json
pub fn json(self: *Ctx, value: anytype, opts: ResponseOptions) !Response
A JSON response: value (a struct, a slice, anything std.json can write) serialized, with Content-Type: application/json.
return c.json(.{ .id = post.id, .title = post.title }, .{});
return c.json(.{ .errors = errors }, .{ .status = .unprocessable_entity });
Ctx.text
pub fn text(_: *Ctx, content: []const u8, opts: ResponseOptions) !Response
A plain text response (UTF-8). content is not copied: it must stay valid until the response is sent (a literal, or memory from c.arena).
Ctx.html
pub fn html(_: *Ctx, content: []const u8, opts: ResponseOptions) !Response
An HTML response from a string you already have. It is sent as it is: nothing is escaped. For a page, view renders a template and escapes the data.
Ctx.download
pub fn download(self: *Ctx, bytes: []const u8, opts: DownloadOptions) !Response
Hands bytes to the client as a file: Content-Disposition with a safe file name, the given content type and X-Content-Type-Options: nosniff, so a browser never interprets the file as something else. Content-Length is written by the server, as for every response.
bytes is not copied: it must stay valid until the response is sent. Memory from c.arena does, and is freed with the request.
error.InvalidContentType when the content type is not a single line of the form type/subtype.
Ctx.render
pub fn render(self: *Ctx, tmpl: []const u8, data: anytype, opts: ResponseOptions) !Response
Renders a template given as text (not a file) with data, as HTML. view is the usual one: it finds the template by name and knows the layout and the components.
Ctx.view
pub fn view(self: *Ctx, name: []const u8, data: anytype, opts: ResponseOptions) !Response
Renders a template of the app with data and answers it as HTML.
name is the feature and the file: "posts/index" is src/features/posts/views/index.html. data is a struct; its fields are the names the template reads, and everything is HTML-escaped unless it is a spider.RawHtml. Templates also see current_user when someone is signed in.
return c.view("posts/show", .{ .title = post.title, .post = post }, .{});
error.TemplateNotFound when there is no such template, error.ViewsNotConfigured when Config.views_dir is null.
Ctx.viewFragment
pub fn viewFragment(self: *Ctx, template_name: []const u8, component_name: []const u8, data: anytype, opts: ResponseOptions) !Response
Renders one component of a template, without the layout or the rest of the page: what an htmx request that swaps a part of the page wants. component_name is a component defined in template_name or a component file. error.ComponentNotFound when there is none.
return c.viewFragment("posts/index", "PostList", .{ .posts = posts }, .{});
Ctx.getBody
pub fn getBody(self: *Ctx) ?[]const u8
The request body as it arrived (the same as the body field).
Ctx.bodyJson
pub fn bodyJson(self: *Ctx, comptime T: type) !T
The JSON body parsed into T, allocated in c.arena. Fields of the JSON that T does not have are ignored; a field T needs and the JSON lacks is an error (error.MissingField), as is JSON that does not parse (error.SyntaxError, error.UnexpectedToken, ...). error.BodyEmpty when there is no body. A body that parses and does not fit T in another way (a number too big for its field, a number written as bad text, an array of the wrong length) is error.InvalidJson, with the parser's own name for it in c.errorDetail(). All of these answer 400 by default.
const Input = struct { title: []const u8, body: []const u8 = "" };
const input = try c.bodyJson(Input);
Ctx.parseMultipart
pub fn parseMultipart(self: *Ctx) !@import("../binding/multipart.zig").MultipartData
The parts of a multipart/form-data body: the fields and the uploaded files, allocated in c.arena. error.BodyEmpty, error.MissingContentType or error.InvalidBoundary when the request is not one; error.InvalidMultipartEncoding, error.MissingFieldName or error.BoundaryTooLong when its body is malformed. All answer 400 by default.
Ctx.parseForm
pub fn parseForm(self: *Ctx, comptime T: type) !T
The fields of a submitted form as a T, whether it came as application/x-www-form-urlencoded or multipart/form-data.
const Input = struct { title: []const u8 = "", body: []const u8 = "" };
const input = try c.parseForm(Input);
A field of T is []const u8, i32, i64, u32, f32, f64, bool, spider.UploadedFile (multipart forms), or an optional of one of them; another type is a compile error. Text is copied into c.arena.
No field is required. One the form does not send takes the default declared in T; without a default it is null when optional, else "" or 0. A number field left blank counts as not sent. A bool is true for "true", "1" and "on", and false when it is not sent, whatever its default: a browser sends nothing for an unchecked box. Check what must be there after parsing.
error.InvalidNumber (a 400) when a number field holds text that is not a number of the field's type ("12,50", "abc", a value too big); error.BodyEmpty when the request has no body; error.MissingField when a multipart form lacks a non-optional UploadedFile.
Ctx.isHtmx
pub fn isHtmx(self: *Ctx) bool
True for a request htmx made (the HX-Request header).
Ctx.isBoosted
pub fn isBoosted(self: *Ctx) bool
True for a navigation htmx made through hx-boost (the HX-Boosted header).
Ctx.requestType
pub fn requestType(self: *Ctx) ?[]const u8
Raw getter for the HX-Request-Type header, introduced in htmx 4.0. Confirmed values per the official htmx docs: "partial" (a fragment swap) or "full" (a full-page request, including boosted navigation and history restores). Returns null on htmx 2.x requests — that version never sends this header — and on non-htmx requests.
This is a thin, unopinionated wrapper around header(), mirroring isHtmx()/isBoosted(). Most call sites should prefer requestKind() below, which already resolves the 2.x/4.0 difference for you.
Ctx.RequestKind
pub const RequestKind = enum
Consolidated classification of an htmx request, spanning both the 2.x header set (HX-Request, HX-Boosted) and the 4.0 header (HX-Request-Type: partial|full).
Values
fragment: A fragment/partial swap. - htmx 4.0:HX-Request-Type: partial. - htmx 2.x:HX-Requestpresent andHX-Boostedabsent.boosted: A boosted navigation (hx-boost) — an AJAX request that represents real navigation, not a UI fragment swap. - htmx 4.0 and 2.x:HX-Boostedpresent.full: A full, non-htmx request — direct navigation, refresh, or a plain (non-AJAX, non-boosted) link/form submission. - htmx 4.0:HX-Request-Type: full. - htmx 2.x: none of the htmx headers present.
Ctx.requestKind
pub fn requestKind(self: *Ctx) RequestKind
Resolves RequestKind for the current request, automatically bridging htmx 2.x and 4.0: it checks HX-Request-Type (4.0) first, and falls back to the HX-Request/HX-Boosted header pair (2.x) when that header is absent. There is no .history_restore variant — htmx 4.0 removed the history-restore request case and always issues a full request for history navigation.
isHtmx() and isBoosted() remain fully valid and are unaffected by this addition — requestKind() is a new convenience on top of them, not a replacement. Existing call sites using isHtmx()/isBoosted() keep working exactly as before.
Ctx.cookie
pub fn cookie(self: *Ctx, name: []const u8) ?[]const u8
The value of the cookie name as the browser sent it, or null. A cookie written with .encode = true is read with cookieDecoded.
Ctx.cookieDecoded
pub fn cookieDecoded(self: *Ctx, name: []const u8) ?[]const u8
A cookie written with .encode = true, decoded. A value that was not encoded comes back as it is, unless it contains a %XX.
Ctx.withCookie
pub fn withCookie(self: *Ctx, name: []const u8, value: []const u8, opts: CookieOptions) !ResponseOptions
Response options that set one cookie:
return c.redirectWith("/", try c.withCookie("theme", "dark", .{ .max_age = 86400 * 365 }));
error.InvalidCookie for a name or value a cookie cannot hold; text a person typed needs .encode = true.
Ctx.setCookie
pub fn setCookie(
self: *Ctx,
name: []const u8,
value: []const u8,
opts: CookieOptions,
) ![]const u8
The Set-Cookie value for name=value with opts. A name, value or attribute that could inject another attribute or header (;, CR/LF, control characters; in names also =, spaces and separators) is error.InvalidCookie. Spaces and UTF-8 in values are accepted, as browsers do.
Ctx.htmx
pub fn htmx(self: *Ctx, h: HtmxHeaders) ![]const [2][]const u8
htmx response headers for ResponseOptions.headers, allocated in c.arena:
return c.html(body, .{ .headers = try c.htmx(.{
.retarget = "#form",
.reswap = .outerHTML,
.trigger = try c.hxEvent("spider:toast", .{ .message = "Saved", .type = "success" }),
}) });
A value containing CR/LF is error.InvalidHeaderValue.
Ctx.hxEvent
pub fn hxEvent(self: *Ctx, name: []const u8, data: anytype) ![]const u8
HX-Trigger JSON carrying data: {"name":<data>} (non-ASCII escaped, so it is a valid header value). For HtmxHeaders.trigger*.
Ctx.deleteCookie
pub fn deleteCookie(self: *Ctx, name: []const u8, opts: CookieOptions) ![]const u8
The Set-Cookie value that removes name: empty value, Max-Age=0. Pass the same path/domain it was set with, or the browser keeps it.
Ctx.query
pub fn query(self: *Ctx, name: []const u8) ?[]const u8
The query string value of name exactly as it came in the URL: NOT percent-decoded (?q=Jo%C3%A3o+Silva gives "Jo%C3%A3o+Silva"). For text a user typed (a search box, a name) use queryDecoded. Null when the name is not there or has no =; when it is repeated, the first one.
Ctx.queryDecoded
pub fn queryDecoded(self: *Ctx, name: []const u8) ?[]const u8
The query string value of name, decoded once like a form field: + → space, %2B → "+", %C3%A3 → "ã"; an invalid escape is kept as typed. Don't decode the result again (a literal "%2F" would become "/"). Allocated in the request arena; on allocation failure the raw value is returned.
Ctx.header
pub fn header(self: *Ctx, name: []const u8) ?[]const u8
The value of a request header, or null. The name is matched without regard to case: c.header("content-type"). A header the client sent on several lines is one value, the lines joined with ", " in the order received (Cookie with "; ").
Ctx.peerAddress
pub fn peerAddress(self: *Ctx) ?[]const u8
The immediate TCP peer's address (no port), allocated in arena. Null only when that allocation fails.
Behind a reverse proxy, this is the proxy's address, not the original client's — use clientIp() with Config.trusted_proxies in that case (not the raw X-Forwarded-For, which clients can forge). This exists for the direct-connection case (no proxy in front, e.g. local dev or a LAN device hitting the server directly), where no proxy ever adds that header, and callers would otherwise have no way to identify who connected.
Ctx.clientIp
pub fn clientIp(self: *Ctx) ?[]const u8
The client's address: the TCP peer, or — when the peer is one of Config.trusted_proxies — the first untrusted address in X-Forwarded-For (right to left). Use this for logs, audit and rate limits; a client can write X-Forwarded-For itself, so reading the header directly lets anyone pick their own address.
Ctx.redirectWith
pub fn redirectWith(self: *Ctx, url: []const u8, opts: ResponseOptions) !Response
A redirect that also sends headers or cookies:
return c.redirectWith("/posts", try c.withCookie("author", name, .{ .encode = true }));
Answers 303 See Other, the status for "saved, now GET this page"; .status set to another 3xx is kept. url is not copied.
Ctx.htmxRedirect
pub fn htmxRedirect(self: *Ctx, url: []const u8) !Response
Where to go after a form, whoever posted it. A form htmx posted gets the address in an HX-Redirect header (with a 200): a 3xx there is followed by the browser itself, and htmx receives the next page as the content to swap into the form's target. Any other request gets a 303 See Other. Works with htmx 2 and htmx 4.
const id = try repository.create(c.arena, input);
return c.htmxRedirect(try std.fmt.allocPrint(c.arena, "/posts/{d}", .{id}));
error.InvalidHeaderValue for a url with a line break.
Ctx.redirect
pub fn redirect(self: *Ctx, url: []const u8) !Response
A 302 redirect to url. After a form was saved, redirectWith is the better one: it answers 303, and can set a cookie; htmxRedirect is the one for a form that htmx may have posted.
Ctx.wsHub
pub fn wsHub(self: *Ctx) *Hub
The hub of the WebSocket route this request is on. Only a request to a WebSocket route (Server.ws) has one: called from any other handler it panics.
Ctx.sseHub
pub fn sseHub(self: *Ctx) *Hub
The hub of the app's SSE streams, to publish events from a handler: c.sseHub().emit("post_created", .{ .id = id }). Panics when the app has no SSE hub: the server creates it for the first SSE route (Server.sse, Group.sse), sseInterval, sseHeartbeat, sseSweep or feature job.
Ctx.getOrgByRole
pub fn getOrgByRole(self: *Ctx, role: []const u8) ?[]const u8
The id of the first organization where the user holds role, or null.
Ctx.hasOrgRole
pub fn hasOrgRole(self: *Ctx, role: []const u8) bool
True when the user holds role in any of their organizations. hasActiveOrgRole looks at the selected one only.
Ctx.activeOrgId
pub fn activeOrgId(self: *Ctx) ?[]const u8
Org the current request acts on, when one was selected — by the auth provider (active_org_cookie) or by an app middleware via setActiveOrg. Null means "no selection", not "no orgs".
Ctx.setActiveOrg
pub fn setActiveOrg(self: *Ctx, org_id: []const u8) !void
Marks org_id as the org this request acts on. requireOrgRoles and hasActiveOrgRole then only accept roles held in THIS org. Setting an org the user isn't a member of is allowed and simply grants no org role.
Ctx.isOrgMember
pub fn isOrgMember(self: *Ctx, org_id: []const u8) bool
True when org_id is one of the user's organizations: the ones the auth provider read from the token, or the app added with addOrgRole.
Ctx.hasActiveOrgRole
pub fn hasActiveOrgRole(self: *Ctx, role: []const u8) bool
True when the user holds role in the active org. With no active org selected, falls back to "in any of the user's orgs" (same as hasOrgRole).
Ctx.orgRoleIn
pub fn orgRoleIn(self: *Ctx, wanted: []const []const u8, org_filter: ?[]const u8) bool
Shared by hasActiveOrgRole and rbac.requireOrgRoles: does any _auth_org_{i} entry (restricted to org_filter when non-null) carry one of wanted?
Ctx.hasRole
pub fn hasRole(self: *Ctx, role: []const u8) bool
True when the user holds role (a role of the account, not of an organization): what .roles on a route checks.
Ctx.User
pub const User = struct
Who a request is from, for setUser.
Fields
id: []const u8email: ?[]const u8 = nullname: ?[]const u8 = null
Ctx.setUser
pub fn setUser(self: *Ctx, user: User) !void
Marks the request as coming from user (what .authenticated checks).
Ctx.userId
pub fn userId(self: *Ctx) ?[]const u8
The logged-in user's id: what setUser stored (the token's subject for jwks/keycloak/clerk, the session's user for spider.session), or the HS256 auth middleware's user id. Null for an anonymous request.
Ctx.addRole
pub fn addRole(self: *Ctx, role: []const u8) !void
Grants the user a role (what .roles and hasRole check).
Ctx.setRoles
pub fn setRoles(self: *Ctx, list: []const []const u8) !void
Replaces the user's roles (e.g. the token's) with list.
Ctx.roles
pub fn roles(self: *Ctx) ![]const []const u8
The user's roles, in the order they were granted.
Ctx.OrgRole
pub const OrgRole = struct
One role of the user inside one organization, for addOrgRole.
Fields
org_id: []const u8org_name: []const u8 = ""role: []const u8
Ctx.addOrgRole
pub fn addOrgRole(self: *Ctx, r: OrgRole) !void
Grants the user a role inside an organization (what .org_roles, isOrgMember and hasActiveOrgRole check). One call per role.
Ctx.loaded
pub fn loaded(self: *Ctx, comptime T: type) ?*T
The resource the route's spider.resourcePolicy loaded and allowed, when it is a T (handlers can also take spider.Loaded(T)). Null when the route has none, or it is of another type.
Ctx.setLoaded
pub fn setLoaded(self: *Ctx, comptime T: type, value: *T) void
Hands value (allocated for the request, e.g. in arena) on to the handler as loaded(T). resourcePolicy calls it; so can an app's own middleware that loads what a route works on.
Ctx.route
pub fn route(self: *const Ctx) RouteMeta
The matched route's declarations (see routing/route_config.zig).
Ctx.hasRoute
pub fn hasRoute(self: *const Ctx) bool
False for a request no route matched: it is on its way to a 404, and route() is then an empty config. A middleware that guards routes lets such a request through.
Ctx.getPath
pub fn getPath(self: *Ctx) []const u8
The target of the request as it was sent: the path and, when there is one, the query string (/posts?q=zig).
Ctx.getMethod
pub fn getMethod(self: *Ctx) []const u8
The request method in capitals: "GET", "POST", ...
spider.NextFn
pub const NextFn = *const fn (*Ctx) anyerror!Response
What a middleware calls to let the request go on: the next middleware, or the handler.
spider.MiddlewareFn
pub const MiddlewareFn = *const fn (*Ctx, NextFn) anyerror!Response
A middleware: runs around the handler. It answers by itself, or calls next(c) and may change the response on the way back.
fn noStore(c: *spider.Ctx, next: spider.NextFn) !spider.Response {
var res = try next(c);
const headers = try c.arena.alloc([2][]const u8, res.headers.len + 1);
@memcpy(headers[0..res.headers.len], res.headers);
headers[res.headers.len] = .{ "Cache-Control", "no-store" };
res.headers = headers;
return res;
}
Register it with Server.use, Server.useAt or Group.use.
spider.ErrorHandler
pub const ErrorHandler = *const fn (*Ctx, anyerror) anyerror!Response
The function given to Server.onError: every error a handler, a middleware or an extractor returns ends up there, and error.NotFound for a request no route matches. spider.errorHandler(.{}) makes a ready one.
spider.Response
pub const Response = struct
What a handler returns. Build it with the methods of Ctx (c.json, c.view, c.redirect, ...); a middleware may change its fields after next(c).
Fields
status: std.http.Status = .okbody: ?[]const u8 = null: Null is sent as an empty body.content_type: []const u8 = "text/plain"headers: []const [2][]const u8 = &.{}: Extra headers, as.{ name, value }pairs. The server addscontent-type,X-Request-Idand the length itself, then sends these, then the cookies.cookies: []const [2][]const u8 = &.{}:.{ name, full Set-Cookie value }pairs, each sent as oneSet-Cookieheader.raw: bool = false: The handler already wrote to the connection itself (SSE, WebSocket): the server sends nothing more and closes the connection.
spider.DownloadOptions
pub const DownloadOptions = struct
Options of Ctx.download.
Fields
filename: []const u8: The name the browser offers to save the file under. It may come from user data: it is cleaned before it reaches the header (see core/download.zig), and falls back todownloadwhen nothing of it can be kept.content_type: []const u8 = content_types.binarydisposition: download_mod.Disposition = .attachment:.attachmentsaves the file;.@"inline"lets the browser show it (a PDF, an image).status: std.http.Status = .okheaders: []const [2][]const u8 = &.{}: Sent after the download's own headers.cookies: []const [2][]const u8 = &.{}
spider.content_types
pub const content_types = struct
Content types for the files apps usually hand out.
content_types.binary
pub const binary = "application/octet-stream"
For anything else: the browser saves it without trying to open it.
spider.statusForError
pub fn statusForError(err: anyerror) std.http.Status
Default HTTP status for an error returned by a handler, middleware or extractor. Used when the app has no onError, and exposed so an app's own handler can fall back to it for errors it doesn't special-case.
- 404:
error.NotFound. - 403:
error.Forbidden. - 401:
error.Unauthorized; a token that fails verification (error.Expired,error.InvalidSignature,error.InvalidFormat);error.OAuthCodeRejected. - 400:
error.BadRequest; input that cannot be bound (error.MissingPathParam,error.InvalidPathParam,error.BodyEmpty,error.BodyUnreadable,error.MissingField,error.MissingFieldName,error.MissingContentType,error.InvalidBoundary,error.BoundaryTooLong,error.InvalidMultipartEncoding, and the JSON errorserror.SyntaxError,error.UnexpectedEndOfInput,error.UnexpectedToken,error.UnknownField,error.DuplicateField,error.InvalidNumber,error.InvalidEnumTag);error.OAuthStateMismatch,error.OAuthCodeMissing; and the Postgres errors about a value (error.InvalidTextRepresentation,error.InvalidUUID,error.StringDataRightTruncation,error.NumericValueOutOfRange,error.InvalidDatetimeFormat,error.DatetimeFieldOverflow,error.NotNullViolation,error.CheckViolation). - 409:
error.UniqueViolation,error.ForeignKeyViolation,error.ExclusionViolation. - 413:
error.PayloadTooLarge. - 422:
error.RaisedException(a RAISE in a trigger or a function). - 502:
error.OAuthProfileFailed. - 503:
error.SerializationFailure,error.DeadlockDetected,error.LockNotAvailable,error.QueryCanceled. - 500: every other error.
Related
What the declarations above take or give back, without a name of its own in spider.
CookieOptions
pub const CookieOptions = struct
The attributes of a cookie, for Ctx.withCookie, Ctx.setCookie and Ctx.deleteCookie. The defaults are the safe ones: not readable from JavaScript, sent over HTTPS only, SameSite=Lax, for the whole site.
Fields
value: []const u8 = "": Not used: the value is the argument of the function.http_only: bool = true:HttpOnly: JavaScript in the page cannot read the cookie.secure: bool = true:Secure: sent over HTTPS only. Browsers still send it tohttp://localhost.same_site: []const u8 = "Lax":SameSite: "Lax", "Strict" or "None".path: []const u8 = "/"max_age: ?u32 = null: Seconds the browser keeps it. Null: until the browser closes.domain: ?[]const u8 = null: Domain attribute (null: host-only, the safer default).encode: bool = false: Percent-encode the value. For text a person typed (a name with an accent, a;): without it such a value is error.InvalidCookie or depends on the browser. Read it back withCtx.cookieDecoded.
HtmxHeaders
pub const HtmxHeaders = struct
htmx response headers, built by Ctx.htmx. htmx 2 and htmx 4 read the same ones, except the two marked below.
Fields
trigger: ?[]const u8 = null: HX-Trigger: an event name, or JSON fromCtx.hxEventto send data.trigger_after_swap: ?[]const u8 = null: HX-Trigger-After-Swap. htmx 2 only: htmx 4 ignores the header (usetrigger).trigger_after_settle: ?[]const u8 = null: HX-Trigger-After-Settle. htmx 2 only: htmx 4 ignores the header (usetrigger).retarget: ?[]const u8 = nullreswap: ?Swap = nullreselect: ?[]const u8 = nullpush_url: ?[]const u8 = nullreplace_url: ?[]const u8 = nullredirect: ?[]const u8 = null: Client-side redirect (full page load).location: ?[]const u8 = null: Client-side navigation without a full reload (a path or JSON).refresh: bool = false
HtmxHeaders.Swap
pub const Swap = enum { innerHTML, outerHTML, textContent, beforebegin, afterbegin, beforeend, afterend, delete, none }
How htmx puts the response into the page: the values of hx-swap.
ResponseOptions
pub const ResponseOptions = struct
The last argument of Ctx.json, Ctx.text, Ctx.html, Ctx.view and the others that build a response. .{} is a 200 with no extra header.
return c.json(.{ .id = id }, .{ .status = .created });
Fields
status: std.http.Status = .okheaders: []const [2][]const u8 = &.{}: Extra response headers, as.{ name, value }pairs. They must stay valid until the response is sent: allocate them inc.arena.cookies: []const [2][]const u8 = &.{}:.{ name, full Set-Cookie value }pairs: each is sent as oneSet-Cookieheader with the second item as its value (the name is not sent).Ctx.withCookieis the short way to set one cookie.
content_types
pub const content_types = download_mod.content_types
Content types for downloads: content_types.xlsx, .csv, .pdf, .binary.