Skip to content

gRPC Scripting

gRPC scripting is in Beta. Open a gRPC request and use the Script (Beta) tab to write hooks with the 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.

grpc-scripting

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.metadata is 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

  1. Open a gRPC request.
  2. Go to the Script (Beta) tab.
  3. Choose a hook and write JavaScript using the bru.grpc APIs. Autocomplete and lint cover bru.grpc.*.
  4. Send the request. Tests from After Message Receive and After Call End appear in the Tests tab.

```yaml api-request.yml scripts: - type: grpc:before-call-start code: | bru.grpc.request.metadata.upsert("x-trace-id", bru.interpolate("{{$guid}}")); - type: grpc:before-message-send code: | console.log("sending", bru.grpc.request.message.data); - type: grpc:after-message-receive code: | test("message has greeting", function () { expect(bru.grpc.response.message.data).to.have.property("greeting"); }); - type: grpc:after-call-end code: | test("call succeeded", function () { expect(bru.grpc.response.statusCode).to.equal(0); });

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

API Description Hooks
bru.grpc.request.metadata.get(key) Value for key BCS–ACE (read)
bru.grpc.request.metadata.one(key) Full { key, value } entry BCS–ACE (read)
bru.grpc.request.metadata.toObject() All entries as a map BCS–ACE (read)
bru.grpc.request.metadata.has(key, value?) Whether the key exists BCS–ACE (read)
bru.grpc.request.metadata.all() All entries as an array BCS–ACE (read)
bru.grpc.request.metadata.count() Number of entries BCS–ACE (read)
bru.grpc.request.metadata.indexOf(item) Index of a key or { key, value } BCS–ACE (read)
bru.grpc.request.metadata.find / filter / map / each / reduce Query and iterate BCS–ACE (read)
bru.grpc.request.metadata.upsert(key, value) Insert or update by key BCS (write)
bru.grpc.request.metadata.add(entry) Upsert from { key, value } BCS (write)
bru.grpc.request.metadata.remove(key) Remove by key BCS (write)
bru.grpc.request.metadata.clear() Remove all entries BCS (write)
bru.grpc.response.metadata.get / toObject / has / all / count / find… Server initial metadata AMR, ACE
bru.grpc.response.trailers.get / toObject / has / all / count / find… Trailing metadata and status ACE

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.