jgodstime/laravel-postman-generator

Generate Postman Collection v2.1 files from Laravel API routes.

Maintainers

Package info

github.com/jgodstime/laravel-postman-generator

pkg:composer/jgodstime/laravel-postman-generator

Transparency log

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-08-30 19:30 UTC

This package is auto-updated.

Last update: 2026-08-30 21:18:36 UTC


README

jgodstime/laravel-postman-generator creates a Postman Collection v2.1 from your registered Laravel API routes — and, by running your existing test suite, captures the real response (status code and body) your API actually returned for each route, instead of a guessed example. No manual collection maintenance required.

Compatibility

PHP 8.2+ and Laravel 10, 11, 12, or 13.

Install

composer require jgodstime/laravel-postman-generator
php artisan vendor:publish --tag=postman-generator-config
php artisan postman:generate

No Postman account or API key is needed for this — it just writes a collection file to disk. --push/--create (below) are optional if you want the collection synced to Postman's cloud directly.

php artisan postman:generate --with-test-responses

No test changes required — any route an existing test already hits through the HTTP kernel ($this->getJson(...), $this->postJson(...), etc.) gets its real captured example automatically. See Captured examples below for how to pin a specific example when several tests cover the same route, and Breaking-change detection for turning these real captures into an automated contract check in CI.

For example, a POST /api/v1/auth/login route validated by a FormRequest gets a request body generated from its rules:

{
    "email": "jane.doe@example.test",
    "password": "Example value"
}

and, because a test already exercises this route, the response is the actual body your app returned — not a guess:

{
    "success": true,
    "message": "You are logged in",
    "data": {
        "id": 42,
        "name": "Jane Doe",
        "email": "jane.doe@example.test",
        "role": "customer",
        "token": "1|abcdefghijklmnopqrstuvwxyz0123456789ABCDEF"
    }
}

Quick start

  1. Generate the file. php artisan postman:generate writes to postman/api.postman_collection.json by default. Use --output=path/to/file.json to change it, or --dry-run to validate without writing.

  2. Check which routes are included. Only routes with the api middleware, or under a configured prefix, are picked up — everything else (web routes, etc.) is skipped. The prefix list defaults to ['api'] and is set via api_prefixes in the published config; if your API routes use a different prefix (or none), add it there or tag the route with ->middleware('api').

  3. Import into Postman. Open Postman → Import → select the generated file. Every route becomes a request, organized into folders (see below).

  4. Check your collection variables. Every generated request uses {{base_url}} (defaults to your app's config('app.url') / APP_URL, falling back to http://localhost if unset) and, for authenticated routes, {{access_token}} (empty by default — set this under the collection's Variables tab). Route parameters and validation fields that reference a related model (exists:/uuid: rules, e.g. {{recipient_type_id}}) also become variables, seeded with a placeholder value (1) — replace them with real IDs for your environment.

How requests are organized

Routes are grouped into folders based on how they're declared:

  • A route inside Route::prefix('auth')->group(...) (or any nested prefix, e.g. Route::prefix('v1/customer')->group(...)) is placed in a folder matching that prefix chain.
  • Within a group, a route that dips into its own plural sub-resource (e.g. customer/orders, customer/uploads) gets a nested subfolder for that resource, rather than being flattened into the parent folder.
  • A route with no explicit group (declared directly under the outer API prefix) gets its own top-level folder named after its first URI segment — so GET /keepsakes and GET /keepsakes/{id} both land in a keepsakes folder instead of one generic catch-all.

Request bodies and query parameters are generated from the route's FormRequest or inline $request->validate([...]) rules where available; routes with neither get a minimal example.

Generated test scripts

Every request gets a Postman Tests script attached automatically, so the collection isn't just a reference — it can run as an actual regression check, in the Postman client or headlessly via newman run collection.json in CI.

  • Every request always gets a status-code assertion.
  • A route with a real captured response (from --with-test-responses — see below) also gets assertions that its top-level response keys are present. Routes without a real capture only get the status-code check: asserting on a synthetic guessed body would just check our own guess against itself, so we don't.
  • Nothing asserts exact values — captured data like timestamps or IDs will never match again, only presence is checked.

Disable this entirely by setting generate_test_scripts to false in the config (or POSTMAN_GENERATOR_TEST_SCRIPTS=false).

Configuration

Published to config/postman-generator.php:

Key Env variable Default Purpose
collection_name POSTMAN_COLLECTION_NAME your app's config('app.name') Name shown in Postman.
base_url POSTMAN_GENERATOR_BASE_URL your app's config('app.url') / APP_URL Seeds the {{base_url}} collection variable every request uses.
output postman/api.postman_collection.json Where the collection file is written.
api_prefixes ['api'] Which URI prefixes count as API routes (in addition to any route tagged with the api middleware).
capture_path storage_path('framework/cache/postman-examples.json') Where captured test responses (--with-test-responses) are cached between runs.
generate_test_scripts POSTMAN_GENERATOR_TEST_SCRIPTS true Whether to attach the auto-generated Postman test scripts described above.
baseline_path POSTMAN_GENERATOR_BASELINE_PATH postman/examples.baseline.json Accepted-state file for --check-breaking-changes. Commit this to git, unlike capture_path.
api_key POSTMAN_API_KEY Required for --push/--create. Never included in the generated file.
workspace_id POSTMAN_WORKSPACE_ID Used by --create if --workspace= isn't passed.
collection_id POSTMAN_COLLECTION_ID Required for --push.

Commands

php artisan postman:generate --with-test-responses
php artisan postman:generate --push
php artisan postman:generate --create --workspace=<workspace-id>

--push updates the collection at POSTMAN_COLLECTION_ID and asks for confirmation interactively (it tries to preserve existing item IDs by matching method + URL, but Postman may still recreate an item it can't safely match — linked monitors or mocks on that item would need to be re-linked). --create posts a new collection to the given workspace and prints its ID/UID; it never writes to .env, so save the printed ID yourself if you want to --push to it later.

Breaking-change detection

Since --with-test-responses captures real response data, that data can also be diffed run-over-run to catch actual API contract changes — not just "did the docs change," but "did a response shape or status code actually change."

# accept the current captures as the new baseline (do this once, commit the file)
php artisan postman:generate --with-test-responses --update-baseline

# in CI: compare fresh captures against the committed baseline, fail on breaking changes
php artisan postman:generate --with-test-responses --check-breaking-changes

--check-breaking-changes flags: a route that no longer has a captured example (removed, or no longer covered by a test), a changed status code, or a response key that disappeared. A newly added response key is reported but not treated as breaking — additive changes don't break existing consumers. No value or type assertions — captured data like timestamps or IDs is expected to differ between runs.

Both flags require --with-test-responses (they need real captured data to compare) and are mutually exclusive with each other. baseline_path (see Configuration) is meant to be committed to git, unlike capture_path.

Captured examples

--with-test-responses runs your whole test suite under APP_ENV=testing before generating. Any request an existing test already makes through the HTTP kernel ($this->getJson(...), $this->postJson(...), etc.) is captured automatically — no test changes required. Fixtures are reset and rewritten fresh on every run and saved to the configured cache path. Do not use production data in tests that exercise real routes.

If several tests hit the same route, whichever runs first wins for that route. To force a specific response (or to have one at all when nothing else covers that route), record it explicitly — an explicit call always wins over an auto-captured one, regardless of run order:

use Jgodstime\LaravelPostmanGenerator\PostmanExample;

it('has a case-folder example', function () {
    $response = $this->getJson('/api/v1/case-folders/1')->assertOk();
    PostmanExample::record('GET api/v1/case-folders/{caseFolderId}', $response);
});

The route key must exactly match "{METHOD} {route uri}" as it's registered — no leading slash, and {param} placeholders literal rather than the concrete value the test used.

CI

Run php artisan postman:generate --dry-run in CI, or generate to a committed path and diff it. Run package tests with composer test.

Contributing

See CONTRIBUTING.md. Please run composer format, composer analyse, and composer test before opening a pull request.

Security

Do not report security vulnerabilities through public issues. See SECURITY.md.

License

The MIT License (MIT). See LICENSE.md.