Beyond the Basics
Most developers and QA engineers know Postman as a graphical tool for sending HTTP requests and inspecting responses. But Postman has evolved into a complete API development and testing platform capable of running fully automated test suites in CI/CD pipelines, simulating backend services that do not exist yet, monitoring production API health, and orchestrating complex multi-step API workflows.
This guide covers the advanced features that transform Postman from a request-sending GUI into a serious API automation platform: Newman CLI for headless execution, mock servers for frontend development without a backend, monitors for production uptime checks, and pre-request scripts for dynamic authentication. These are the features I rely on daily in API-heavy QA workflows.
Architecture Overview
Understanding how Postman components relate helps you design test collections that work both interactively and in automated pipelines.
Collections
(headless runner)
(GitHub Actions / Jenkins)
(Postman cloud)
(HTML / JUnit / JSON)
Collections are the central artifact — they store requests, test scripts, and pre-request scripts. Newman runs collections headlessly for CI integration. Postman Monitors run collections on a schedule in Postman's cloud. Both feed into reporting systems for pass/fail tracking.
Environments and Variables
The most important Postman feature for real-world test automation is the environment system. Instead of hardcoding URLs and credentials into your requests, you reference variables using double-curly-brace syntax.
// In request URL field
{{baseUrl}}/api/v1/users/{{userId}}
// In request headers
Authorization: Bearer {{token}}
// In request body (JSON)
{
"username": "{{testUsername}}",
"password": "{{testPassword}}"
}
Variable scopes (highest to lowest precedence)
| Scope | Where Set | Survives Session | Best For |
|---|---|---|---|
| Local | pm.variables.set() in script |
No — current run only | Temporary values within a request chain |
| Data | CSV/JSON data file (Collection Runner) | No — per iteration | Data-driven test iterations |
| Environment | Environment panel, pm.environment.set() |
Yes — within environment | Base URLs, auth tokens, stage-specific config |
| Collection | Collection variables panel, pm.collectionVariables.set() |
Yes — within collection | Shared IDs and state across the collection |
| Global | Global panel, pm.globals.set() |
Yes — across all workspaces | Rarely — use environment scope instead |
Setting variables in test scripts
// In Tests tab — extract response data and store for next request
const responseJson = pm.response.json();
// Store user ID from create-user response for use in subsequent requests
pm.environment.set("createdUserId", responseJson.data.id);
// Store a value in collection scope for cross-request sharing
pm.collectionVariables.set("orderId", responseJson.order.id);
Pre-Request Scripts
Pre-request scripts run before the actual request is sent. The most powerful use case is dynamic authentication token refresh — instead of manually updating your token when it expires, a pre-request script fetches a fresh token and injects it automatically before every request.
// Pre-Request Script — Token refresh before each request
const tokenUrl = pm.environment.get("authBaseUrl") + "/oauth/token";
const clientId = pm.environment.get("clientId");
const clientSecret = pm.environment.get("clientSecret");
// Check if token is still valid (stored with expiry timestamp)
const tokenExpiry = pm.environment.get("tokenExpiry");
const now = Date.now();
if (!tokenExpiry || now >= parseInt(tokenExpiry)) {
// Token missing or expired — fetch a new one
pm.sendRequest({
url: tokenUrl,
method: "POST",
header: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: {
mode: "urlencoded",
urlencoded: [
{ key: "grant_type", value: "client_credentials" },
{ key: "client_id", value: clientId },
{ key: "client_secret", value: clientSecret },
{ key: "scope", value: "read write" }
]
}
}, function(err, response) {
if (!err && response.code === 200) {
const body = response.json();
pm.environment.set("token", body.access_token);
// Store expiry: current time + expires_in seconds (converted to ms)
const expiryMs = now + (body.expires_in - 60) * 1000; // 60s buffer
pm.environment.set("tokenExpiry", expiryMs.toString());
} else {
console.error("Token refresh failed:", err || response.text());
}
});
}
This script runs before every request in the collection. It checks if the stored token has expired. If so, it fires a synchronous pm.sendRequest() to fetch a new token, stores it in the environment, and records the expiry timestamp. The actual request then uses {{token}} in its Authorization header, which now resolves to the freshly fetched token.
Test Assertions
Postman's test scripts use the pm.test() function with Chai-style assertions via pm.expect(). Here is a complete set of test assertions covering the most common API test scenarios:
// =========================================
// Status code checks
// =========================================
pm.test("Status is 200 OK", () => {
pm.response.to.have.status(200);
});
pm.test("Status is in 2xx range", () => {
pm.expect(pm.response.code).to.be.within(200, 299);
});
// =========================================
// Response time
// =========================================
pm.test("Response time is under 500ms", () => {
pm.expect(pm.response.responseTime).to.be.below(500);
});
// =========================================
// JSON body assertions
// =========================================
const body = pm.response.json();
pm.test("Response has required fields", () => {
pm.expect(body).to.have.property("id");
pm.expect(body).to.have.property("email");
pm.expect(body).to.have.property("createdAt");
});
pm.test("Email matches expected format", () => {
pm.expect(body.email).to.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
});
pm.test("User is active", () => {
pm.expect(body.status).to.equal("active");
});
pm.test("Items array is not empty", () => {
pm.expect(body.items).to.be.an("array").that.is.not.empty;
});
// =========================================
// JSON Schema validation
// =========================================
const Ajv = require("ajv");
const ajv = new Ajv();
const userSchema = {
type: "object",
required: ["id", "email", "firstName", "lastName", "status"],
properties: {
id: { type: "integer" },
email: { type: "string" },
firstName: { type: "string" },
lastName: { type: "string" },
status: { type: "string", enum: ["active", "inactive", "pending"] }
},
additionalProperties: false
};
pm.test("Response matches user schema", () => {
const valid = ajv.validate(userSchema, body);
pm.expect(valid, ajv.errorsText()).to.be.true;
});
// =========================================
// Header checks
// =========================================
pm.test("Content-Type is JSON", () => {
pm.expect(pm.response.headers.get("Content-Type"))
.to.include("application/json");
});
pm.test("Response has X-Request-ID header", () => {
pm.expect(pm.response.headers.get("X-Request-ID")).to.not.be.null;
});
Collection Runner
The Collection Runner executes an entire Postman collection in sequence, with configurable data files for data-driven testing. Access it from the collection's context menu or the Runner tab.
Data-driven testing with CSV
Create a CSV file where each row is one test iteration:
username,password,expectedStatus,expectedRole
admin@example.com,Admin123!,200,admin
user@example.com,User456!,200,standard
invalid@example.com,wrongpass,401,
locked@example.com,Pass789!,403,
In your test script, reference the data file columns as variables:
// Test script using data-file variables
pm.test(`Login returns ${pm.iterationData.get("expectedStatus")}`, () => {
pm.response.to.have.status(parseInt(pm.iterationData.get("expectedStatus")));
});
if (pm.response.code === 200) {
pm.test("Role matches expected", () => {
const body = pm.response.json();
pm.expect(body.user.role).to.equal(pm.iterationData.get("expectedRole"));
});
}
In Collection Runner, select your CSV file, set iteration count to match row count, and run. Each row becomes one complete pass through the collection with that row's variable values.
Newman CLI
Newman is Postman's official command-line collection runner. It runs Postman collections without the GUI — essential for CI pipeline integration.
# Install Newman globally
npm install -g newman
# Basic run — collection file + environment file
newman run collection.json -e environment.json
# With data file for data-driven testing
newman run collection.json -e environment.json -d test-data.csv
# Set iteration count
newman run collection.json -e environment.json -n 3
# Set request timeout (ms)
newman run collection.json -e environment.json --timeout-request 5000
# Fail on first error (useful for CI fast-fail)
newman run collection.json -e environment.json --bail
# Verbose output for debugging
newman run collection.json -e environment.json --verbose
Newman exit codes for CI
Newman exits with code 0 on success and non-zero on failure:
0— All tests passed1— Newman error (runtime failure, invalid collection, network error)- Any non-zero — Treat as build failure in CI
CI systems (GitHub Actions, Jenkins, CircleCI) automatically fail the build step if the process exits non-zero, so Newman integrates naturally without extra configuration.
Newman HTML Reporter
The default Newman output is console text. The newman-reporter-htmlextra package produces a rich HTML report with charts, request/response details, and failure highlights.
# Install htmlextra reporter
npm install -g newman-reporter-htmlextra
# Run with HTML report output
newman run collection.json \
-e environment.json \
--reporters cli,htmlextra \
--reporter-htmlextra-export reports/newman-report.html \
--reporter-htmlextra-title "API Test Results — Sprint 42" \
--reporter-htmlextra-showOnlyFails
The --reporter-htmlextra-showOnlyFails flag produces a report that highlights only failing tests — useful for a quick failures-only view in large collections.
Newman in GitHub Actions
# .github/workflows/api-tests.yml
name: API Tests — Newman
on:
push:
branches: [main, develop]
pull_request:
jobs:
api-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Newman and reporters
run: npm install -g newman newman-reporter-htmlextra
- name: Run API tests
env:
API_BASE_URL: ${{ secrets.API_BASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
run: |
# Inject environment variables into Postman environment file
envsubst < postman/environment-template.json > postman/environment.json
newman run postman/collection.json \
-e postman/environment.json \
--reporters cli,htmlextra,junit \
--reporter-htmlextra-export reports/api-report.html \
--reporter-junit-export reports/results.xml \
--bail
- name: Upload test reports
uses: actions/upload-artifact@v4
if: always()
with:
name: api-test-reports
path: reports/
- name: Publish test results
uses: dorny/test-reporter@v1
if: always()
with:
name: Newman API Tests
path: reports/results.xml
reporter: java-junit
Mock Servers in Postman
Mock servers let you simulate API responses before the real backend exists. This is invaluable for frontend teams that need an API to develop against while the backend is still being built.
Creating a mock from a collection
- In your collection, add example responses to each request (right-click request → Add Example)
- Go to the collection's "..." menu → Mock Collection
- Name the mock server and choose the environment
- Postman generates a mock URL:
https://<mock-id>.mock.pstmn.io
How mock matching works
When a request hits the mock URL, Postman matches it against your saved examples by:
- HTTP method (GET, POST, etc.)
- URL path
- Query parameters (optional)
- Request headers (optional)
- Request body (optional)
The best-matching example's response is returned. You can save multiple examples per request to simulate different scenarios — success, not found, validation error — and the mock returns the matching one based on query parameters or headers.
// Using the mock URL in your frontend or test
const response = await fetch(
"https://abc123.mock.pstmn.io/api/v1/users/42",
{
headers: {
"x-api-key": "your-postman-api-key",
"x-mock-match-request-body": "true"
}
}
);
Postman Monitors
Monitors run your Postman collection on a scheduled basis from Postman's cloud infrastructure — checking that your live API is behaving correctly without you running anything locally. They are the simplest form of production API monitoring available.
Setting up a monitor
- In your workspace, click "Monitor Collection" from the collection menu
- Set the schedule: every 5 minutes, hourly, daily — down to 1-minute intervals on paid plans
- Choose the environment (your production environment variables)
- Configure notification: email on failure, Slack webhook, PagerDuty integration
What to monitor with Postman Monitors
- Health check endpoint:
GET /health→ status 200, response time under 300ms - Authentication flow: Token generation → authenticated request → logout
- Critical business flows: Create order → verify order → cancel order
- Third-party integrations: Verify your payment gateway and email service endpoints respond correctly
Postman Flows
Postman Flows is a visual, no-code/low-code interface for building API workflows. You drag-and-drop blocks representing API requests, conditional logic, data transformation, and loops — connecting them with wires that represent data flow.
Flows replaces complex pre-request and test script chains for orchestration logic. Instead of writing JavaScript to extract a user ID from one response and inject it into the next request, you visually connect the output of the first block to the input of the second. Flows is particularly useful for:
- Onboarding team members who are not comfortable with JavaScript
- Documenting complex API workflows visually for stakeholders
- Rapid prototyping of multi-step API sequences
- Replacing basic integration glue code with a maintained visual spec
Tool Comparison
| Feature | Postman | Insomnia | Bruno | REST Client (VS Code) |
|---|---|---|---|---|
| GUI | Full-featured desktop app + web | Desktop app | Desktop app (open source) | VS Code extension only |
| CLI Runner | Newman (excellent) | Inso (limited) | Bruno CLI (growing) | None |
| Team Collaboration | Cloud workspaces, versioning | Git sync, cloud | Git-native (files in repo) | Via Git (plain .http files) |
| Mock Servers | Built-in (cloud) | Yes (Kong Studio) | No | No |
| Monitors | Built-in (cloud, paid for <5min) | No | No | No |
| Test Scripting | JavaScript (full pm.* API) | JavaScript (limited) | JavaScript (growing) | No |
| Pricing | Free tier; $14/user/mo (Basic) | Free (OSS); paid cloud sync | Free (OSS) | Free |
Best Practices
1. Always use environments for base URLs — never hardcode
Every URL in your collection should be {{baseUrl}}/path/to/resource. Switching from dev to staging to production should be a single environment dropdown change — not a find-and-replace across 50 requests.
2. Put token refresh in a collection-level pre-request script, not in individual requests
If you copy the token refresh pre-request script into each request individually, you will inevitably miss some requests and have inconsistent behavior. Place it in the collection's Pre-request Script tab (accessible from the collection's Edit panel) to apply it automatically to every request in the collection.
3. Keep test scripts in the collection, not scattered in individual requests
Store common assertion helpers in collection-level scripts and call them from individual request tests. This avoids duplicating assertion logic across dozens of requests. When an assertion pattern changes, you update it in one place.
4. Export and version-control your collections
Export your Postman collection as JSON and commit it to your repository alongside your application code. This ensures the collection evolves with the API and team members can import it without syncing through Postman cloud accounts.
project/
├── postman/
│ ├── User-Service.postman_collection.json
│ ├── Order-Service.postman_collection.json
│ ├── dev.postman_environment.json
│ └── staging.postman_environment.json
5. Use folder structure within collections
Organize requests into folders by resource or feature: Authentication, Users, Orders, Products. This makes the Collection Runner's folder-level execution useful — you can run only the Authentication folder to smoke-test auth before running the full suite.
Back to Blog