Convert OpenAPI 3.x, Swagger 2.0, GraphQL schemas, gRPC Protocol Buffer specs, and AsyncAPI WebSocket specs into fully functional Bruno API collections with one command. Available as a CLI tool and a programmatic JavaScript/TypeScript library.
gen-bruno ./openapi.yaml ./output- OpenAPI 3.x — Full support for paths, parameters, request bodies, responses, and examples
- Swagger 2.0 — Legacy Swagger specs automatically normalized and converted
- GraphQL — SDL schemas converted to executable Bruno GraphQL requests, grouped into query/mutation folders
- gRPC —
.protoservice definitions converted to native Bruno gRPC requests, including streaming modes - AsyncAPI WebSockets — AsyncAPI 2.x and 3.x
ws/wssservers converted to native Bruno WebSocket requests and message bodies - Authentication — Bearer, Basic, API Key, OAuth2, and OpenID Connect security schemes
- Examples — Preserves spec examples and auto-generates placeholder values from schemas
- Folder grouping — Organize by tags, URL paths, or flat structure
- Plugins — Transform the IR or modify .bru output before writing
- Config files —
brunogen.config.ymlfor reproducible, team-shared settings - Dual ESM/CJS — Works in both
importandrequireprojects
# Install globally
npm install -g gen-bruno
# Generate from a spec file
gen-bruno ./openapi.yaml ./output
# Or use npx without installing
npx gen-bruno ./openapi.yaml ./output
# Open the output folder in Bruno — done# Global CLI
npm install -g gen-bruno
# Or as a project dependency
npm install gen-brunoRequires Node.js 24+.
gen-bruno [spec] [output] [options]| Flag | Description | Default |
|---|---|---|
<spec> |
Path to an OpenAPI, Swagger, GraphQL, gRPC .proto, or AsyncAPI WebSocket spec file |
(required) |
[output] |
Output directory for the Bruno collection | ./bruno-output |
--format <tag|path|flat> |
Folder grouping strategy | tag |
--tests |
Generate post-response test assertions | off |
--dry-run |
Print tree to stdout without writing files | off |
--config <path> |
Path to config file | auto-discover |
--verbose |
Include stack traces in error output | off |
# OpenAPI spec → Bruno collection (grouped by tag)
gen-bruno ./openapi.yaml ./output
# GraphQL schema with test assertions
gen-bruno ./schema.graphql ./output --tests
# Protocol Buffer service definition → native Bruno gRPC requests
gen-bruno ./greeter.proto ./output
# AsyncAPI WebSocket specification → native Bruno WebSocket requests
gen-bruno ./chat.asyncapi.yaml ./output
# Dry run to preview parsed spec
gen-bruno ./openapi.yaml ./output --dry-run
# Group requests by URL path instead of tags
gen-bruno ./openapi.yaml ./output --format path
# Use a config file with overrides
gen-bruno --config ./brunogen.config.yaml --tests --verbosetag(default) — Groups requests by their OpenAPI tags into Bruno folderspath— Groups by URL path structure (/users,/users/{id}, etc.)flat— All requests in a single folder, no grouping
Place brunogen.config.yml (or .json) in your project root. The CLI auto-discovers it.
# brunogen.config.yml
spec: ./openapi.yaml # Default spec path (CLI <spec> overrides)
output: ./bruno-output # Default output directory
format: tag # Folder grouping: tag | path | flat
tests: false # Generate test assertions
force: false # Overwrite existing output directory
# plugins: # Plugin file paths (optional)
# - ./plugins/add-headers.jsMerge priority (lowest → highest): Built-in defaults < config file < CLI flags.
Use programmatically in your own scripts. Supports both ESM and CommonJS.
import { generate, parse } from "gen-bruno";
const ir = await parse("./openapi.yaml");
const result = await generate(ir, {
outputDir: "./output",
grouping: "tag", // "tag" | "path" | "flat"
generateTests: false, // Include post-response test assertions
force: false, // Overwrite existing output
});
console.log(`Generated ${result.filesWritten.length} files`);Unified spec parser. Accepts file paths to OpenAPI 3.x YAML/JSON, Swagger 2.0, GraphQL SDL, gRPC Protocol Buffer (.proto), or AsyncAPI 2.x/3.x WebSocket YAML/JSON files.
import { parse } from "gen-bruno";
// OpenAPI 3.x
const ir = await parse("./openapi.yaml");
// Swagger 2.0
const ir = await parse("./swagger.json");
// GraphQL SDL
const ir = await parse("./schema.graphql");
// gRPC Protocol Buffer service definition
const ir = await parse("./greeter.proto");
// AsyncAPI WebSocket document
const ir = await parse("./chat.asyncapi.yaml");
// Explicitly select a format when inline content is ambiguous
const inline = await parse({
content: 'openapi: "3.0.0"\ninfo: { title: API, version: "1" }\npaths: {}',
format: "openapi",
});Each root Query and Mutation field becomes a POST /graphql request. Generated requests use Bruno's native body:graphql and body:graphql:vars blocks, with an operation document, typed variables, and a schema-derived response selection. The collection defaults baseUrl to http://localhost:4000; update it in Bruno to match your API server.
Returns a CollectionIR object with normalized endpoints, parameters, security schemes, and response schemas.
Each service RPC in a .proto file becomes a native Bruno gRPC request. The generated collection includes the input proto at protos/<filename> and each request references it with a relative protoPath, making the collection portable. The default baseUrl is http://localhost:50051; update it in Bruno to match your gRPC server. Unary, client-streaming, server-streaming, and bidirectional-streaming RPCs map to Bruno's corresponding request modes.
AsyncAPI 2.x and 3.x documents with ws or wss servers are supported. Each publish/send and subscribe/receive operation becomes a native Bruno WebSocket request. Send operations include JSON, text, or XML message examples; receive operations create a connection request so the documented server messages can be observed. The collection's baseUrl is taken from the first WebSocket server.
Fluent builder API for chaining configuration and generation.
import { CollectionBuilder } from "gen-bruno";
// From a spec file
const result = await CollectionBuilder.fromSpec("./openapi.yaml")
.withOptions({ grouping: "path", generateTests: true })
.withPlugins([myHeaderPlugin])
.generate("./output");
// Reuse the same builder for different outputs
const builder = CollectionBuilder.fromSpec("./openapi.yaml");
await builder.withOptions({ grouping: "tag" }).generate("./out-tag");
await builder.withOptions({ grouping: "flat" }).generate("./out-flat");
// Or keep the output directory in the immutable builder options
await CollectionBuilder.fromSpec("./openapi.yaml")
.withOptions({ outputDir: "./output", grouping: "tag" })
.generate();Load and merge a config file from the filesystem.
import { loadConfig } from "gen-bruno";
// Auto-discover config in CWD
const config = await loadConfig();
// Load specific config file
const config = await loadConfig(process.cwd(), "./config/brunogen.yaml");Three-layer config merge utility.
import { mergeConfig } from "gen-bruno";
const merged = mergeConfig(
{ format: "tag", tests: false }, // Built-in defaults
{ spec: "./api.yaml", tests: true }, // Config file
{ format: "path" }, // CLI flags
);
// Result: { spec: "./api.yaml", format: "path", tests: true }Plugins let you transform the IR before generation or modify .bru content before writing.
interface Plugin {
name: string;
hooks: {
// Transform the CollectionIR after parsing, before generation
transformIR?: (ir: CollectionIR, ctx: PluginContext) => Promise<CollectionIR>;
// Transform file content right before writing
preOutput?: (content: string, ctx: PreOutputContext) => Promise<string>;
};
}const addHeaderPlugin: Plugin = {
name: "add-custom-header",
hooks: {
preOutput: async (content) => {
return content.includes("method:")
? content + "\nheader: X-Custom-Header: my-value\n"
: content;
},
},
};import type { Plugin } from "gen-bruno";
const authPlugin: Plugin = {
name: "auth-header-injector",
hooks: {
transformIR: async (ir) => ({
...ir,
endpoints: ir.endpoints.map((ep) => ({
...ep,
headers: [
...(ep.headers || []),
{ name: "Authorization", value: "{{apiKey}}", required: true },
],
})),
}),
},
};
// Use via CollectionBuilder
await CollectionBuilder.fromSpec("./openapi.yaml")
.withPlugins([authPlugin])
.generate("./output");Via config file:
plugins:
- ./plugins/add-headers.js
- ./plugins/auth-injector.jsVia builder API:
await CollectionBuilder.fromSpec("./api.yaml")
.withPlugins([plugin1, plugin2])
.generate("./output");Via generate() function:
await generate(ir, {
outputDir: "./output",
plugins: [myPlugin],
});The generator produces a complete Bruno collection directory:
output/
├── bruno.json # Bruno collection metadata
├── collection.bru # Collection-level config (meta, auth, vars, docs)
├── environments/
│ └── default.bru # Environment variables (auth tokens, etc.)
├── Weather/ # Folder per tag (or path segment)
│ └── getweatherdata.bru # One .bru file per endpoint
└── Users/
├── createuser.bru
├── getuser.bru
└── updateuser.bru
Each .bru file contains the full request definition — method, URL, parameters, headers, body, auth, tests, and docs — ready to use in Bruno.
- Verify the spec file path is correct and accessible
- Check that the spec is valid OpenAPI 3.x, Swagger 2.0, or GraphQL SDL
- Run with
--verboseto see detailed error output including stack traces
- Validate your spec first:
gen-bruno ./openapi.yaml ./output --dry-run - Check for unsupported features in the warnings output
- Ensure
$refreferences in your spec resolve correctly
- Ensure you're running Node.js 24 or higher (
node --version) - For ESM projects: use
import { generate } from "gen-bruno" - For CommonJS projects: use
const { generate } = require("gen-bruno")
- Verify the plugin file path is correct (relative to CWD or absolute)
- Ensure the plugin exports an object with
nameandhooksproperties - Check that each hook is an async function returning the transformed value
- Place
brunogen.config.ymlorbrunogen.config.jsonin your project root - Or specify explicitly:
gen-bruno --config ./path/to/config.yaml
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes
- Run tests:
npm test - Ensure build passes:
npm run build - Check linting:
npm run lint && npm run format:check - Open a Pull Request
All contributions welcome — bug fixes, new features, docs improvements, and real-world spec validation.