Zig 0.17.0

Ctx and Response

On this page:

spider.Ctx

zig
pub const Ctx = struct

The request, and what builds the response: every handler and middleware receives a *Ctx.

zig
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 use getMethod, getPath, header and query instead.
  • 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 :name segments 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 with userId, hasRole and 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 a Content-Length. See bodyJson and parseForm.

Ctx.requestId

zig
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

zig
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

zig
pub fn errorDetail(self: *Ctx) ?[]const u8

The reason attached with setErrorDetail, if any.

Ctx.wantsJson

zig
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

zig
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.

zig
const res = try spider.http_client.get(c.io(), c.arena, url, .{});

Ctx.json

zig
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.

zig
return c.json(.{ .id = post.id, .title = post.title }, .{});
return c.json(.{ .errors = errors }, .{ .status = .unprocessable_entity });

Ctx.text

zig
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

zig
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

zig
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

zig
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

zig
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.

zig
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

zig
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.

zig
return c.viewFragment("posts/index", "PostList", .{ .posts = posts }, .{});

Ctx.getBody

zig
pub fn getBody(self: *Ctx) ?[]const u8

The request body as it arrived (the same as the body field).

Ctx.bodyJson

zig
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.

zig
const Input = struct { title: []const u8, body: []const u8 = "" };
const input = try c.bodyJson(Input);

Ctx.parseMultipart

zig
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

zig
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.

zig
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

zig
pub fn isHtmx(self: *Ctx) bool

True for a request htmx made (the HX-Request header).

Ctx.isBoosted

zig
pub fn isBoosted(self: *Ctx) bool

True for a navigation htmx made through hx-boost (the HX-Boosted header).

Ctx.requestType

zig
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

zig
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-Request present and HX-Boosted absent.
  • 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-Boosted present.
  • 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

zig
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.

zig
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

zig
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

zig
pub fn withCookie(self: *Ctx, name: []const u8, value: []const u8, opts: CookieOptions) !ResponseOptions

Response options that set one cookie:

zig
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

zig
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

zig
pub fn htmx(self: *Ctx, h: HtmxHeaders) ![]const [2][]const u8

htmx response headers for ResponseOptions.headers, allocated in c.arena:

zig
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

zig
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

zig
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

zig
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

zig
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

zig
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

zig
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

zig
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

zig
pub fn redirectWith(self: *Ctx, url: []const u8, opts: ResponseOptions) !Response

A redirect that also sends headers or cookies:

zig
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

zig
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.

zig
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

zig
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

zig
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

zig
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

zig
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

zig
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

zig
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

zig
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

zig
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

zig
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

zig
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

zig
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

zig
pub const User = struct

Who a request is from, for setUser.

Fields

  • id: []const u8
  • email: ?[]const u8 = null
  • name: ?[]const u8 = null

Ctx.setUser

zig
pub fn setUser(self: *Ctx, user: User) !void

Marks the request as coming from user (what .authenticated checks).

Ctx.userId

zig
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

zig
pub fn addRole(self: *Ctx, role: []const u8) !void

Grants the user a role (what .roles and hasRole check).

Ctx.setRoles

zig
pub fn setRoles(self: *Ctx, list: []const []const u8) !void

Replaces the user's roles (e.g. the token's) with list.

Ctx.roles

zig
pub fn roles(self: *Ctx) ![]const []const u8

The user's roles, in the order they were granted.

Ctx.OrgRole

zig
pub const OrgRole = struct

One role of the user inside one organization, for addOrgRole.

Fields

  • org_id: []const u8
  • org_name: []const u8 = ""
  • role: []const u8

Ctx.addOrgRole

zig
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

zig
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

zig
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

zig
pub fn route(self: *const Ctx) RouteMeta

The matched route's declarations (see routing/route_config.zig).

Ctx.hasRoute

zig
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

zig
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

zig
pub fn getMethod(self: *Ctx) []const u8

The request method in capitals: "GET", "POST", ...

spider.NextFn

zig
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

zig
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.

zig
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

zig
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

zig
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 = .ok
  • body: ?[]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 adds content-type, X-Request-Id and the length itself, then sends these, then the cookies.
  • cookies: []const [2][]const u8 = &.{}: .{ name, full Set-Cookie value } pairs, each sent as one Set-Cookie header.
  • raw: bool = false: The handler already wrote to the connection itself (SSE, WebSocket): the server sends nothing more and closes the connection.

spider.DownloadOptions

zig
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 to download when nothing of it can be kept.
  • content_type: []const u8 = content_types.binary
  • disposition: download_mod.Disposition = .attachment: .attachment saves the file; .@"inline" lets the browser show it (a PDF, an image).
  • status: std.http.Status = .ok
  • headers: []const [2][]const u8 = &.{}: Sent after the download's own headers.
  • cookies: []const [2][]const u8 = &.{}

spider.content_types

zig
pub const content_types = struct

Content types for the files apps usually hand out.

content_types.binary

zig
pub const binary = "application/octet-stream"

For anything else: the browser saves it without trying to open it.

spider.statusForError

zig
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 errors error.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.

What the declarations above take or give back, without a name of its own in spider.

CookieOptions

zig
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 to http://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 with Ctx.cookieDecoded.

HtmxHeaders

zig
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 from Ctx.hxEvent to send data.
  • trigger_after_swap: ?[]const u8 = null: HX-Trigger-After-Swap. htmx 2 only: htmx 4 ignores the header (use trigger).
  • trigger_after_settle: ?[]const u8 = null: HX-Trigger-After-Settle. htmx 2 only: htmx 4 ignores the header (use trigger).
  • retarget: ?[]const u8 = null
  • reswap: ?Swap = null
  • reselect: ?[]const u8 = null
  • push_url: ?[]const u8 = null
  • replace_url: ?[]const u8 = null
  • redirect: ?[]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

zig
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

zig
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.

zig
return c.json(.{ .id = id }, .{ .status = .created });

Fields

  • status: std.http.Status = .ok
  • headers: []const [2][]const u8 = &.{}: Extra response headers, as .{ name, value } pairs. They must stay valid until the response is sent: allocate them in c.arena.
  • cookies: []const [2][]const u8 = &.{}: .{ name, full Set-Cookie value } pairs: each is sent as one Set-Cookie header with the second item as its value (the name is not sent). Ctx.withCookie is the short way to set one cookie.

content_types

zig
pub const content_types = download_mod.content_types

Content types for downloads: content_types.xlsx, .csv, .pdf, .binary.