gRPC Scripting
bru.grpc APIs.
HTTP requests in Bruno already support pre-request scripts, post-response scripts, tests, and assertions. gRPC requests now have the same kind of automation at the request level: four hooks along the call lifecycle, a read-only bru.grpc.* API, and test() / expect() on inbound messages and at the end of the call.
This ships for interactive runs in the app. Collection- and folder-level gRPC scripts, Collection Runner, and CLI are out of scope.
For usage examples, see gRPC APIs in the JavaScript Reference.

Lifecycle hooks
A gRPC call is a connection that exchanges messages and ends with a status. Bruno runs four hooks:
| Hook | When it runs | Typical use |
|---|---|---|
Before Call Start (grpc:before-call-start) |
Once, before the RPC is invoked | Set metadata, read call info |
Before Message Send (grpc:before-message-send) |
Before each outbound message (once for unary / server-streaming; per message for client-streaming / bidi) | Inspect the message about to go on the wire (read-only) |
After Message Receive (grpc:after-message-receive) |
After each inbound message (once for unary / client-streaming; per message for server-streaming / bidi) | Validate that message with test() / expect() |
After Call End (grpc:after-call-end) |
Once after the call ends, errors, or is cancelled | Assert status, trailers, all received messages, duration |
- Message lists are always read-only. You cannot add, replace, send, or delete messages from a script.
bru.grpc.request.metadatais writable only in Before Call Start.bru.grpc.response.*is read-only and exists only in After Message Receive and After Call End.
The usual bru.* helpers (variables, sendRequest, sleep, interpolate, OAuth2 credential helpers, console) work in every hook. bru.cookies.* and bru.runner.* are not available on gRPC requests.
How to add a script
- Open a gRPC request.
- Go to the Script (Beta) tab.
- Choose a hook and write JavaScript using the
bru.grpcAPIs. Autocomplete and lint coverbru.grpc.*. - Send the request. Tests from After Message Receive and After Call End appear in the Tests tab.
```bru api-request.bru
script:grpc:before-call-start {
bru.grpc.request.metadata.upsert("authorization", "Bearer " + bru.getEnvVar("token"));
}
script:grpc:before-message-send {
console.log(bru.grpc.request.message.data);
}
script:grpc:after-message-receive {
test("greeting is present", function () {
expect(bru.grpc.response.message.data.greeting).to.be.a("string");
});
}
script:grpc:after-call-end {
test("status is OK", function () {
expect(bru.grpc.response.statusCode).to.equal(0);
});
}
API reference (tables)
All gRPC data lives under bru.grpc.request.* (client) and bru.grpc.response.* (server). A message is { data, timestamp } — data is the protobuf payload; timestamp is epoch milliseconds (Bruno-specific).
Hook abbreviations: BCS Before Call Start · BMS Before Message Send · AMR After Message Receive · ACE After Call End.
Messages
request.messages is what was transmitted, not what is authored in the UI. It is empty in Before Call Start.
| API | Description | Hooks |
|---|---|---|
bru.grpc.request.messages.get(i) |
Transmitted message at index i (default 0) |
BCS, BMS, AMR, ACE (read) |
bru.grpc.request.messages.all() |
All transmitted messages | BCS, BMS, AMR, ACE (read) |
bru.grpc.request.messages.count() |
Number of transmitted messages | BCS, BMS, AMR, ACE (read) |
bru.grpc.request.messages.find / filter / map / each / reduce |
Query and iterate | BCS, BMS, AMR, ACE (read) |
bru.grpc.request.message.data |
Current outbound payload | BMS |
bru.grpc.request.message.timestamp |
When the BMS hook ran (epoch ms) | BMS |
bru.grpc.response.message.data |
Current received payload | AMR |
bru.grpc.response.message.timestamp |
When this message was received | AMR |
bru.grpc.response.messages.get(i) |
Received message at index i |
ACE |
bru.grpc.response.messages.all() |
All received messages | ACE |
bru.grpc.response.messages.count() |
Number of received messages | ACE |
bru.grpc.response.messages.find / filter / map / each / reduce |
Query and iterate | ACE |
Singular hooks (BMS, AMR) always see one message. request.messages has many items for client-streaming and bidi; response.messages has many for server-streaming and bidi.
Metadata
Metadata entries are { key, value }. Keys ending in -bin are binary (values stored as base64). Key matching is case-insensitive.
request.metadata is readable in all four hooks and writable only in BCS. response.metadata is server initial metadata. response.trailers is trailing metadata and includes grpc-status / grpc-message.
Call info and tests
| API | Description | Hooks |
|---|---|---|
bru.grpc.request.url |
Server URL (host:port) |
all (read) |
bru.grpc.request.method |
Full method path | all (read) |
bru.grpc.request.methodType |
unary, server-streaming, client-streaming, or bidi |
all (read) |
bru.grpc.request.authMode |
Configured auth (default none) |
all (read) |
bru.grpc.request.protoPath |
Proto file path | all (read) |
bru.grpc.request.name |
Request name | all (read) |
bru.grpc.response.statusCode |
gRPC status (0 = OK) |
ACE |
bru.grpc.response.statusText |
Status text | ACE |
bru.grpc.response.duration |
Call duration in ms | ACE |
test(name, fn) |
Define a test | AMR, ACE |
expect(value) |
Chai-style assertion | AMR, ACE |
What is not included
- Changing or sending messages from a script
- Collection- and folder-level gRPC hooks (folder/collection scripts still run for HTTP only)
- Collection Runner and Bruno CLI execution of gRPC scripts
Known gaps: stack traces for errors in Before Message Send / After Message Receive still need polish, and a long-running Before Call Start script has no progress UI before the connection opens.