Skip to content

HTTP Protocol

The trpc package implements the tRPC HTTP wire format on top of net/http.

mux.Handle("/trpc/", trpc.NewHandler(router, "/trpc"))

The base path is stripped before procedure lookup:

URLProcedure
/trpc/user.getuser.get
/trpc/admin.audit.listadmin.audit.list

Path traversal segments . and .. are rejected.

Procedure typeMethod
QueryGET by default. POST only with WithMethodOverride(true).
MutationPOST.
SubscriptionGET or POST, served as SSE after setup succeeds.

Other HTTP methods return METHOD_NOT_SUPPORTED.

For GET, input comes from the input query parameter:

GET /trpc/user.get?input={"id":"1"}

For POST, input comes from the raw request body:

POST /trpc/user.create
Content-Type: application/json
{"name":"Alice","email":"alice@example.com"}

Empty input is passed as the zero value for typed procedures or nil for void procedures.

Normal query and mutation responses are wrapped in the tRPC result envelope:

{
"result": {
"data": {
"id": "1",
"name": "Alice"
}
}
}

Errors use the tRPC error shape:

{
"error": {
"code": -32004,
"message": "procedure not found",
"data": {
"code": "NOT_FOUND",
"httpStatus": 404,
"path": "user.missing"
}
}
}

WithDev(true) adds data.stack for debugging.

Batch requests use ?batch=1 and comma-separated procedure paths.

For GET, the input query parameter is an object keyed by batch index:

GET /trpc/user.get,system.health?batch=1&input={"0":{"id":"1"}}

For POST, the body has the same indexed shape:

{
"0": { "id": "1" },
"1": { "page": 1, "perPage": 20 }
}

The response is an array of individual envelopes. If every item has the same HTTP status, the batch response uses that status. Mixed statuses return HTTP 207 Multi-Status.

Subscriptions cannot be batched.

Set trpc-accept: application/jsonl on a batch request to stream batch results as JSON lines.

GET /trpc/user.get,user.list?batch=1
trpc-accept: application/jsonl

JSONL batch calls execute concurrently. Chunks may arrive out of request order, and per-call errors are represented inside their chunks. The HTTP status is 200 after streaming starts.

trpc.NewHandler builds a procedure map when the handler is created. Register or merge procedures before mounting the handler:

trpcgo.MustQuery(router, "user.get", getUser)
handler := trpc.NewHandler(router, "/trpc")
// This registration is not visible to handler.
trpcgo.MustQuery(router, "user.late", lateHandler)

If you need dynamic routing, construct a new handler after updating registrations.