Skip to main content

Overview

Bru is Bruno’s plain text markup language for defining API requests. It’s human-readable, Git-friendly, and structured around blocks that define different aspects of an HTTP request.
Every .bru file represents a single API request and uses a block-based syntax for organizing request metadata, headers, body, scripts, and tests.

File Structure

A .bru file is composed of blocks. There are three types of blocks:

Dictionary Blocks

Key-value pairs for headers, metadata, auth, etc.

Text Blocks

Multi-line text content for body, scripts, tests

List Blocks

Arrays of items like tags or secret variables

Basic Request Example

Here’s a complete example from the Bruno test suite:
echo json.bru
meta {
  name: echo json
  type: http
  seq: 2
}

post {
  url: {{host}}/api/echo/json
  body: json
  auth: none
}

headers {
  foo: bar
}

auth:basic {
  username: asd
  password: j
}

auth:bearer {
  token: 
}

body:json {
  {
    "hello": "bruno"
  }
}

assert {
  res.status: eq 200
}

script:pre-request {
  bru.setVar("foo", "foo-world-2");
}

tests {
  test("should return json", function() {
    const data = res.getBody();
    expect(res.getBody()).to.eql({
      "hello": "bruno"
    });
  });  
}

Core Blocks

Meta Block

Every request starts with a meta block defining its properties:
meta {
  name: Login Request
  type: http
  seq: 3
}
name
string
required
Display name of the request
type
string
required
Request type: http, graphql-request, grpc-request, ws-request, or js
seq
number
Sequence number for ordering requests

HTTP Method Blocks

Define the HTTP method and URL:
get {
  url: {{host}}/api/users
  body: none
  auth: inherit
}
post {
  url: https://echo.usebruno.com
  body: json
  auth: none
}
put {
  url: {{baseUrl}}/users/{{userId}}
  body: json
  auth: bearer
}
Supported methods: get, post, put, delete, patch, options, head, connect, trace
Use auth: inherit to use authentication configured at the folder or collection level.

Headers Block

Define request headers using key-value pairs:
headers {
  Content-Type: application/json
  foo: bar
  Authorization: Bearer {{token}}
}
Disabling headers:
headers {
  Content-Type: application/json
  ~Disabled-Header: this-wont-be-sent
}
Use the ~ prefix to disable a header without deleting it.

Query Parameters

params:query {
  page: 1
  limit: 20
  ~debug: true
}

Path Parameters

get {
  url: http://localhost:8081/api/echo/path/:path
  auth: inherit
}

params:path {
  path: some-data
}

Authentication Blocks

Bruno supports multiple authentication methods:

Bearer Token

get {
  url: {{host}}/api/auth/bearer/protected
  body: none
  auth: bearer
}

auth:bearer {
  token: {{bearer_auth_token}}
}

Basic Auth

auth:basic {
  username: myuser
  password: mypassword
}

API Key

auth:apikey {
  key: X-API-Key
  value: {{apiKey}}
  placement: header
}

OAuth2

auth:oauth2 {
  grant_type: authorization_code
  callback_url: http://localhost:8080/callback
  authorization_url: https://oauth.example.com/authorize
  access_token_url: https://oauth.example.com/token
  client_id: {{client_id}}
  client_secret: {{client_secret}}
  scope: read write
  state: random-state-string
  pkce: true
}

AWS Signature v4

auth:awsv4 {
  accessKeyId: {{aws_access_key}}
  secretAccessKey: {{aws_secret_key}}
  sessionToken: {{aws_session_token}}
  service: s3
  region: us-east-1
}

Digest Auth

auth:digest {
  username: myuser
  password: mypassword
}

Body Blocks

JSON Body

post {
  url: {{host}}/api/users
  body: json
  auth: none
}

body:json {
  {
    "name": "John Doe",
    "email": "[email protected]",
    "age": 30
  }
}

XML Body

body:xml {
  <?xml version="1.0" encoding="UTF-8"?>
  <user>
    <name>John Doe</name>
    <email>[email protected]</email>
  </user>
}

Text Body

body:text {
  This is plain text content
  that can span multiple lines.
}

Form URL Encoded

body:form-urlencoded {
  username: john
  password: secret123
  remember: true
  ~disabled_field: value
}

Multipart Form

body:multipart-form {
  username: john
  avatar: @file(/path/to/image.jpg)
  documents: @file(/path/file1.pdf|/path/file2.pdf)
}

GraphQL

body:graphql {
  query {
    users(limit: 10) {
      id
      name
      email
    }
  }
}

body:graphql:vars {
  {
    "limit": 10
  }
}

Variables

Pre-request Variables

vars:pre-request {
  collection_pre_var: collection_pre_var_value
  collection_pre_var_token: {{request_pre_var_token}}
  collection-var: collection-var-value
}

Post-response Variables

vars:post-response {
  token: {{res.body.token}}
  userId: {{res.body.id}}
}

Scripts

Pre-request Script

script:pre-request {
  // Set variables before the request
  bru.setVar("timestamp", Date.now());
  bru.setVar("randomId", Math.random().toString(36));
  
  // Modify request
  req.setHeader("X-Request-ID", bru.getVar("randomId"));
}

Post-response Script

script:post-response {
  // Save response data
  if (res.status === 200) {
    const data = res.getBody();
    bru.setEnvVar("authToken", data.token, { persist: true });
    bru.setVar("userId", data.user.id);
  }
}

Tests and Assertions

Assertions Block

assert {
  res.status: eq 200
  res.body.message: Authentication successful
}

Tests Block

tests {
  test("Status code is 200", function() {
    expect(res.getStatus()).to.equal(200);
  });
  
  test("Response has json field", function() {
    const response = res.getBody();
    expect(response).to.have.property('json');
  });
  
  test("Response json has username", function() {
    const response = res.getBody();
    expect(response.json).to.have.property('username');
  });
}

Settings Block

Configure request-specific settings:
settings {
  encodeUrl: true
  followRedirects: true
  maxRedirects: 10
  timeout: 30000
}
encodeUrl
boolean
Whether to URL-encode the request URL (default: true)
followRedirects
boolean
Whether to follow HTTP redirects (default: true)
maxRedirects
number
Maximum number of redirects to follow (0-50)
timeout
number
Request timeout in milliseconds

Documentation Block

Add documentation to your request:
docs {
  # Login Endpoint
  
  This endpoint authenticates a user and returns a JWT token.
  
  ## Authentication
  No authentication required for this endpoint.
  
  ## Response
  Returns a JSON object with:
  - `token`: JWT authentication token
  - `user`: User object with id, name, email
}

Advanced Features

Multiline Text with Delimiters

Use triple quotes for multiline values:
headers {
  X-Custom-Header: '''
    This is a multiline
    header value that spans
    multiple lines
  '''
}

Quoted Keys

Use quotes for keys with special characters:
headers {
  "Content-Type": application/json
  "X-Special-Key-With-Spaces": value
}

Disabling Items

Prefix any key with ~ to disable it:
headers {
  Content-Type: application/json
  ~Debug-Mode: true
}

params:query {
  page: 1
  ~verbose: true
}

Complete Example

Here’s a full-featured request from the Bruno source:
Login Request.bru
meta {
  name: Login Request
  type: http
  seq: 3
}

post {
  url: https://echo.usebruno.com
  body: json
  auth: none
}

headers {
  Content-Type: application/json
}

body:json {
  {
    "username": "testuser",
    "password": "testpass"
  }
}

tests {
  test("Status code is 200", function() {
    expect(res.getStatus()).to.equal(200);
  });
  
  test("Response has json field", function() {
    const response = res.getBody();
    expect(response).to.have.property('json');
  });
  
  test("Response json has username", function() {
    const response = res.getBody();
    expect(response.json).to.have.property('username');
  });
}

Next Steps

Collections

Learn how to organize requests into collections

Environments

Use variables across different environments

Scripting

Write JavaScript to add dynamic behavior

Testing

Write tests and assertions for your APIs

Build docs developers (and LLMs) love