> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usebruno.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Postman to Bruno

Convert a Postman collection and environment into a Bruno collection folder you can open in the app or run with the CLI.

Start by exporting the collection from Postman in the Collection v2.1 format and, if you use one, the environment. See [Postman migration](/get-started/import-export-data/postman-migration) for where to find the export buttons.

## Convert the collection and environment

Install both packages:

```bash theme={null}
npm install @usebruno/converters @usebruno/filestore
```

The converter returns a JavaScript object. Bruno needs a collection folder, so the script below converts the input and writes that folder in one go. It writes [OpenCollection YAML](/opencollection-yaml/overview) files by default. Change `format` to `'bru'` for `.bru` files instead.

<Note>
  The script needs Node.js 18 or later.
</Note>

### 1. Read the arguments

Import the converter, the file writers from `@usebruno/filestore`, and Node's file helpers. Take the paths from the command line and print a usage line if any are missing. `format` is `'yml'` for OpenCollection YAML or `'bru'` for classic `.bru` files.

```javascript theme={null}
import { postmanToBruno, postmanToBrunoEnvironment } from '@usebruno/converters';
import { stringifyCollection, stringifyFolder, stringifyRequest, stringifyEnvironment } from '@usebruno/filestore';
import { mkdir, readFile, writeFile } from 'fs/promises';
import path from 'path';

const [collectionFile, outputDir, environmentFile] = process.argv.slice(2);
if (!collectionFile || !outputDir) {
  console.error('Usage: node convert.mjs <postman-collection.json> <output-folder> [postman-environment.json]');
  process.exit(1);
}

const format = 'yml';
```

### 2. Add the helpers

`safeName` strips characters that are not allowed in file names. `write` saves a file and creates any missing parent folders. `readJson` reads and parses a JSON file.

```javascript theme={null}
const readJson = async (file) => JSON.parse(await readFile(file, 'utf8'));

const safeName = (name) => name.replace(/[<>:"/\\|?*\x00-\x1F]/g, '-');

const write = async (file, content) => {
  await mkdir(path.dirname(file), { recursive: true });
  await writeFile(file, content);
};
```

### 3. Convert

`postmanToBruno` resolves to the Bruno collection and a list of `issues` it could not convert cleanly. If an environment file was given, convert it and add it to the collection so it is written with the rest. See the [API Reference](./api-reference#postmantobruno) for the full signature.

```javascript theme={null}
const { collection, issues } = await postmanToBruno(await readJson(collectionFile));

if (environmentFile) {
  collection.environments.push(postmanToBrunoEnvironment(await readJson(environmentFile)));
}
```

### 4. Write the collection settings and environments

`brunoConfig` holds the collection settings. For `.bru` collections it is written to `bruno.json`. For yml it is embedded in `opencollection.yml`. The collection root holds collection-level auth, headers, and scripts. Each environment gets its own file in the `environments` folder.

```javascript theme={null}
const brunoConfig = { version: collection.version, name: collection.name, type: 'collection', ignore: ['node_modules', '.git'] };

const root = stringifyCollection(collection.root || {}, brunoConfig, { format });
if (format === 'yml') {
  await write(path.join(outputDir, 'opencollection.yml'), root);
} else {
  await write(path.join(outputDir, 'bruno.json'), JSON.stringify(brunoConfig, null, 2));
  await write(path.join(outputDir, 'collection.bru'), root);
}

for (const env of collection.environments || []) {
  await write(path.join(outputDir, 'environments', `${safeName(env.name)}.${format}`), stringifyEnvironment(env, { format }));
}
```

### 5. Write the folders and requests

Each folder becomes a directory and each request a file. Folder-level auth, headers, and scripts go in `folder.yml` or `folder.bru`. A stack is used instead of recursion so deeply nested collections are safe.

```javascript theme={null}
const stack = collection.items.map((item) => [item, outputDir]);
while (stack.length) {
  const [item, parent] = stack.pop();
  if (item.type === 'folder') {
    const folderDir = path.join(parent, safeName(item.name));
    await mkdir(folderDir, { recursive: true });
    if (item.root) await write(path.join(folderDir, `folder.${format}`), stringifyFolder(item.root, { format }));
    for (const child of item.items || []) stack.push([child, folderDir]);
  } else {
    await write(path.join(parent, `${safeName(item.name)}.${format}`), stringifyRequest(item, { format }));
  }
}
```

### 6. Report

Print where the collection was written, then each issue with its severity, the path of the item, and a message.

```javascript theme={null}
console.log(`Collection written to ${outputDir}`);

for (const issue of issues) {
  console.warn(`${issue.severity}: ${issue.path}: ${issue.message}`);
}
```

### 7. Run the script

Run the script with Node.js, passing the paths as arguments.

<CodeGroup>
  ```bash convert.mjs theme={null}
  node convert.mjs <postman-collection.json> <output-folder> [postman-environment.json]
  ```

  ```bash convert.js theme={null}
  node convert.js <postman-collection.json> <output-folder> [postman-environment.json]
  ```
</CodeGroup>

Where:

* `<postman-collection.json>`: The collection exported from Postman
* `<output-folder>`: The folder to create the Bruno collection in. It is created if it does not exist
* `[postman-environment.json]`: Optional. An environment exported from Postman

The output folder looks like this (with `format` set to `'bru'`, the files are `bruno.json`, `collection.bru`, `folder.bru`, and `.bru` requests instead):

```text theme={null}
my-collection/
├── opencollection.yml
├── environments/
│   └── Dev.yml
├── Users/
│   ├── folder.yml
│   ├── List users.yml
│   └── Create user.yml
└── Ping.yml
```

One folder per Postman folder, one file per request, and the environment under `environments`.

### Full script

The complete script, ready to copy. `convert.mjs` is an ES module with top-level `await`. `convert.js` is the same script as CommonJS, with the steps inside an async `main` function.

<CodeGroup>
  ```javascript convert.mjs theme={null}
  // Converts a Postman collection, and optionally an environment, into a Bruno collection folder.
  // Usage: node convert.mjs <postman-collection.json> <output-folder> [postman-environment.json]

  import { postmanToBruno, postmanToBrunoEnvironment } from '@usebruno/converters';
  import { stringifyCollection, stringifyFolder, stringifyRequest, stringifyEnvironment } from '@usebruno/filestore';
  import { mkdir, readFile, writeFile } from 'fs/promises';
  import path from 'path';

  // ---- Arguments ----

  const [collectionFile, outputDir, environmentFile] = process.argv.slice(2);
  if (!collectionFile || !outputDir) {
    console.error('Usage: node convert.mjs <postman-collection.json> <output-folder> [postman-environment.json]');
    process.exit(1);
  }

  // 'yml' (OpenCollection YAML) or 'bru' (classic .bru files)
  const format = 'yml';

  // ---- Helpers ----

  const readJson = async (file) => JSON.parse(await readFile(file, 'utf8'));

  // Strip characters not allowed in file names
  const safeName = (name) => name.replace(/[<>:"/\\|?*\x00-\x1F]/g, '-');

  // Write a file, creating parent folders as needed
  const write = async (file, content) => {
    await mkdir(path.dirname(file), { recursive: true });
    await writeFile(file, content);
  };

  // ---- Convert ----

  // Resolves to the Bruno collection and a list of items that could not be converted cleanly
  const { collection, issues } = await postmanToBruno(await readJson(collectionFile));

  // Add the environment so it is written with the collection
  if (environmentFile) {
    collection.environments.push(postmanToBrunoEnvironment(await readJson(environmentFile)));
  }

  // ---- Write the collection folder ----

  // Collection settings: bruno.json for .bru, embedded in opencollection.yml for yml
  const brunoConfig = { version: collection.version, name: collection.name, type: 'collection', ignore: ['node_modules', '.git'] };

  // Collection-level auth, headers, and scripts
  const root = stringifyCollection(collection.root || {}, brunoConfig, { format });
  if (format === 'yml') {
    await write(path.join(outputDir, 'opencollection.yml'), root);
  } else {
    await write(path.join(outputDir, 'bruno.json'), JSON.stringify(brunoConfig, null, 2));
    await write(path.join(outputDir, 'collection.bru'), root);
  }

  // One file per environment
  for (const env of collection.environments || []) {
    await write(path.join(outputDir, 'environments', `${safeName(env.name)}.${format}`), stringifyEnvironment(env, { format }));
  }

  // Folders become directories, requests become files. A stack avoids recursion on deep trees.
  const stack = collection.items.map((item) => [item, outputDir]);
  while (stack.length) {
    const [item, parent] = stack.pop();
    if (item.type === 'folder') {
      const folderDir = path.join(parent, safeName(item.name));
      await mkdir(folderDir, { recursive: true });
      // Folder-level auth, headers, and scripts
      if (item.root) await write(path.join(folderDir, `folder.${format}`), stringifyFolder(item.root, { format }));
      for (const child of item.items || []) stack.push([child, folderDir]);
    } else {
      await write(path.join(parent, `${safeName(item.name)}.${format}`), stringifyRequest(item, { format }));
    }
  }

  // ---- Report ----

  console.log(`Collection written to ${outputDir}`);

  // Each issue has a severity, the path of the item, and a message
  for (const issue of issues) {
    console.warn(`${issue.severity}: ${issue.path}: ${issue.message}`);
  }
  ```

  ```javascript convert.js theme={null}
  // Converts a Postman collection, and optionally an environment, into a Bruno collection folder.
  // Usage: node convert.js <postman-collection.json> <output-folder> [postman-environment.json]

  const { postmanToBruno, postmanToBrunoEnvironment } = require('@usebruno/converters');
  const { stringifyCollection, stringifyFolder, stringifyRequest, stringifyEnvironment } = require('@usebruno/filestore');
  const { mkdir, readFile, writeFile } = require('fs/promises');
  const path = require('path');

  // ---- Arguments ----

  const [collectionFile, outputDir, environmentFile] = process.argv.slice(2);
  if (!collectionFile || !outputDir) {
    console.error('Usage: node convert.js <postman-collection.json> <output-folder> [postman-environment.json]');
    process.exit(1);
  }

  // 'yml' (OpenCollection YAML) or 'bru' (classic .bru files)
  const format = 'yml';

  // ---- Helpers ----

  const readJson = async (file) => JSON.parse(await readFile(file, 'utf8'));

  // Strip characters not allowed in file names
  const safeName = (name) => name.replace(/[<>:"/\\|?*\x00-\x1F]/g, '-');

  // Write a file, creating parent folders as needed
  const write = async (file, content) => {
    await mkdir(path.dirname(file), { recursive: true });
    await writeFile(file, content);
  };

  // CommonJS cannot use top-level await, so the steps run inside an async function
  async function main() {
    // ---- Convert ----

    // Resolves to the Bruno collection and a list of items that could not be converted cleanly
    const { collection, issues } = await postmanToBruno(await readJson(collectionFile));

    // Add the environment so it is written with the collection
    if (environmentFile) {
      collection.environments.push(postmanToBrunoEnvironment(await readJson(environmentFile)));
    }

    // ---- Write the collection folder ----

    // Collection settings: bruno.json for .bru, embedded in opencollection.yml for yml
    const brunoConfig = { version: collection.version, name: collection.name, type: 'collection', ignore: ['node_modules', '.git'] };

    // Collection-level auth, headers, and scripts
    const root = stringifyCollection(collection.root || {}, brunoConfig, { format });
    if (format === 'yml') {
      await write(path.join(outputDir, 'opencollection.yml'), root);
    } else {
      await write(path.join(outputDir, 'bruno.json'), JSON.stringify(brunoConfig, null, 2));
      await write(path.join(outputDir, 'collection.bru'), root);
    }

    // One file per environment
    for (const env of collection.environments || []) {
      await write(path.join(outputDir, 'environments', `${safeName(env.name)}.${format}`), stringifyEnvironment(env, { format }));
    }

    // Folders become directories, requests become files. A stack avoids recursion on deep trees.
    const stack = collection.items.map((item) => [item, outputDir]);
    while (stack.length) {
      const [item, parent] = stack.pop();
      if (item.type === 'folder') {
        const folderDir = path.join(parent, safeName(item.name));
        await mkdir(folderDir, { recursive: true });
        // Folder-level auth, headers, and scripts
        if (item.root) await write(path.join(folderDir, `folder.${format}`), stringifyFolder(item.root, { format }));
        for (const child of item.items || []) stack.push([child, folderDir]);
      } else {
        await write(path.join(parent, `${safeName(item.name)}.${format}`), stringifyRequest(item, { format }));
      }
    }

    // ---- Report ----

    console.log(`Collection written to ${outputDir}`);

    // Each issue has a severity, the path of the item, and a message
    for (const issue of issues) {
      console.warn(`${issue.severity}: ${issue.path}: ${issue.message}`);
    }
  }

  main();
  ```
</CodeGroup>

## Open the collection in the app or run it with the CLI

If you included an environment, it keeps its Postman name.

Open the folder in the app, or run it with the CLI:

<Tabs>
  <Tab title="Bruno app">
    1. Click the **+** button in the top-left corner and select **Import Collection**.
    2. Choose **Bruno Collection**, then browse to the output folder.
    3. Click **Import**.

    See [Import Collections](/get-started/import-export-data/import-collections) for more detail.
  </Tab>

  <Tab title="Bruno CLI">
    ```bash theme={null}
    cd <output-folder>
    bru run
    ```

    To use an environment, pass its name with `--env`:

    ```bash theme={null}
    bru run --env <environment-name>
    ```

    See [bru run](/bru-cli/run/overview) for more options.
  </Tab>
</Tabs>
