amondar-libs / php-postman
Allow to create postman documentation for your API
Requires
- php: ^8.3
- amondar-libs/class-attributes: ^2.2
- illuminate/support: ^10|^11|^12
- nunomaduro/termwind: ^2.3
- spatie/laravel-package-tools: ^1.92
- symfony/console: ^7.4
Requires (Dev)
- amondar-libs/php-markdown: ^1.3
- driftingly/rector-laravel: ^2.1
- larastan/larastan: ^3.9
- laravel/pint: ^1.24
- orchestra/testbench: ^10.0
- pestphp/pest: ^4.3
- rector/rector: ^2.3
- spatie/pest-plugin-test-time: ^2.2
- symfony/var-dumper: ^7.0
README
A PHP package that generates Postman collections from your application routes. It supports Laravel out of the box and can be extended to work with any PHP framework.
Table of Contents
Installation
Install the package via Composer:
composer require amondar-libs/php-postman
Requirements
- PHP 8.3 or higher
- Laravel 10, 11, or 12 (for Laravel integration)
Laravel Auto-Discovery
The package ships with a Laravel service provider that is auto-discovered. No manual registration is needed. The service provider registers route macros (alias, auth, description, additionalHeaders, structureDepth) that you can use directly on your route definitions.
Basic Usage
Laravel Integration
The quickest way to export your Laravel routes to a Postman collection is via the built-in CLI binary:
./vendor/bin/postman export --laravel --base-url=https://api.example.com --output=postman.json
This will bootstrap your Laravel application, parse all registered routes, and write a Postman collection JSON file.
Standalone CLI
For non-Laravel projects, you can provide a custom route parser class:
./vendor/bin/postman export \ --routes-parser="App\\Documentation\\MyRoutesParser" \ --base-url=https://api.example.com \ --output=postman.json
If --routes-parser is not provided and --laravel is not set, the CLI will interactively ask for the parser class name.
Programmatic Usage
You can also generate collections programmatically in your PHP code:
use Amondar\Postman\Export; use Amondar\Postman\Route\Route; use Amondar\Postman\Route\RouteCollection; use Amondar\Postman\Auth\Bearer; // Build a route collection $routes = new RouteCollection(); $routes->push(new Route( name: 'users.index', path: 'api/users', methods: ['GET'], action: null, )); $routes->push(new Route( name: 'users.store', path: 'api/users', methods: ['POST'], action: null, )); // Export to JSON $json = Export::from($routes, 'https://api.example.com') ->useDefaultAuth(Bearer::make('your-token')) ->toJson('My API Collection'); file_put_contents('postman.json', $json);
Using with Laravel Routes Parser
use Amondar\Postman\Export; use Amondar\Postman\Laravel\LaravelRoutesParser; $parser = new LaravelRoutesParser(); $routes = $parser->parseLaravelRoutes(app('router')->getRoutes()); $json = Export::from($routes, config('app.url')) ->toJson('My Laravel API');
Deep Dive
Authentication
The package supports multiple authentication types that map directly to Postman's authentication options. Set a default auth for the entire collection or assign auth per route.
Bearer Token
use Amondar\Postman\Auth\Bearer; $export = Export::from($routes, $baseUrl) ->useDefaultAuth(Bearer::make('optional-default-token'));
Basic Auth
use Amondar\Postman\Auth\Basic; $export = Export::from($routes, $baseUrl) ->useDefaultAuth(Basic::make('username', 'password'));
OAuth 2.0 — Password Grant
use Amondar\Postman\Auth\OAuthPassword; $export = Export::from($routes, $baseUrl) ->useDefaultAuth(new OAuthPassword( clientId: 'your-client-id', clientSecret: 'your-client-secret', scope: 'read write', )) ->useOAuthTokenPath('/oauth/token');
OAuth 2.0 — Client Credentials
use Amondar\Postman\Auth\OAuthClientCredentials; $export = Export::from($routes, $baseUrl) ->useDefaultAuth(new OAuthClientCredentials( clientId: 'your-client-id', clientSecret: 'your-client-secret', scope: 'read write', )) ->useOAuthTokenPath('/oauth/token');
OAuth 2.0 — Authorization Code with PKCE
use Amondar\Postman\Auth\OAuthCodeWithPKCE; $export = Export::from($routes, $baseUrl) ->useDefaultAuth(new OAuthCodeWithPKCE( clientId: 'your-client-id', callbackUrl: 'https://app.example.com/callback', authUrl: 'https://auth.example.com/authorize', scope: 'openid profile', )) ->useOAuthTokenPath('/oauth/token');
No Authentication
use Amondar\Postman\Auth\None; // This is the default — routes with no auth assigned will use None $export = Export::from($routes, $baseUrl) ->useDefaultAuth(new None());
Per-Route Authentication (Laravel)
When using Laravel, you can set authentication on individual routes or route groups:
use Amondar\Postman\Auth\Bearer; use Amondar\Postman\Auth\Basic; // Single route Route::get('/api/users', [UserController::class, 'index']) ->name('users.index') ->auth(Bearer::make()); // Route group Route::auth(Basic::make())->group(function () { Route::get('/admin/dashboard', [AdminController::class, 'dashboard']) ->name('admin.dashboard'); });
Route Macros (Laravel)
The service provider registers several macros on Laravel's Route, RouteRegistrar, and Router classes:
alias(string $alias)
Override the display name of a route in the Postman collection:
Route::get('/api/v2/users', [UserController::class, 'index']) ->name('api.v2.users.index') ->alias('List All Users');
description(Stringable|string $description)
Add a description to the route. Accepts a plain string, a \Stringable instance, or a class name that implements Stringable:
Route::get('/api/users', [UserController::class, 'index']) ->name('users.index') ->description('Returns a paginated list of all users.');
structureDepth(int $depth)
Control how deeply the route name is used for folder nesting in the Postman collection. By default, all segments except the last one are used as folder names:
// Route name: "api.v2.users.index" // Default folders: api > v2 > users // With structureDepth(0): api // With structureDepth(1): api > v2 Route::get('/api/v2/users', [UserController::class, 'index']) ->name('api.v2.users.index') ->structureDepth(2);
auth(AuthenticationContract $type)
Set the authentication type for a route or route group (see Authentication).
additionalHeaders(array $headers)
Add custom headers to the Postman request:
Route::get('/api/users', [UserController::class, 'index']) ->name('users.index') ->additionalHeaders([ ['key' => 'X-Custom-Header', 'value' => 'custom-value', 'type' => 'text'], ['key' => 'Accept-Language', 'value' => 'en', 'type' => 'text'], ]);
Route Filtering
Filter which routes are included in the exported collection. Filtering is available both via CLI options and programmatically.
CLI Filtering
# Filter by path pattern ./vendor/bin/postman export --laravel --route-paths="api/*" # Filter by route name ./vendor/bin/postman export --laravel --route-names="users.*" # Filter by HTTP method. Case-insensitive. ./vendor/bin/postman export --laravel --route-methods="GET;POST" # Filter by middleware ./vendor/bin/postman export --laravel --route-middlewares="auth:*" # Combine multiple filters (all conditions must match) ./vendor/bin/postman export --laravel \ --route-paths="api/*" \ --route-methods="GET" \ --route-middlewares="auth"
Multiple values can be separated by semicolons or passed as repeated options.
Programmatic Filtering
use Amondar\Postman\Route\RouteFilter; $filter = RouteFilter::apply() ->byPath('api/*') ->byMethod(['GET', 'POST']) ->byName('users.*') ->byMiddleware('auth'); $filteredRoutes = $routes->filterRoutes($filter)->values(); $json = Export::from($filteredRoutes, $baseUrl)->toJson();
Form Data Attributes
Use the #[PostmanFormData] attribute to define request body data for your routes. The attribute can be applied to controller classes or methods and accepts either an inline array or a class implementing Renderable.
Inline Array
use Amondar\Postman\Attributes\PostmanFormData; class UserController { #[PostmanFormData([ 'name' => 'John Doe', 'email' => 'john@example.com' ])] public function store(Request $request) { // ... } }
Using a Renderable Class
use Amondar\Postman\Attributes\PostmanFormData; use Amondar\Postman\Contracts\Renderable; class StoreUserFormData implements Renderable { public function render(): array { // There can be complex logic to generate form data. // Also, you can use faker here. return [ 'name' => 'John Doe', 'email' => 'john@example.com', 'role' => 'admin', ]; } } class UserController { #[PostmanFormData(StoreUserFormData::class)] public function store(Request $request) { // ... } }
To enable attribute scanning, specify the directories to scan using parseDataIn():
$json = Export::from($routes, $baseUrl) ->parseDataIn('app/Http/Controllers', 'app/Http/Requests') ->toJson();
Or via CLI:
./vendor/bin/postman export --laravel --attributes-in=app/Domain --attributes-in=app/Infrastructure
Descriptions
Route descriptions support both plain strings and Stringable objects. This allows integration with markdown libraries or any custom description generator:
use Amondar\Postman\Contracts\Renderable; // Plain string Route::get('/api/users', [UserController::class, 'index']) ->description('Returns a list of users.'); // Stringable class (e.g., from amondar-libs/php-markdown) Route::get('/api/users', [UserController::class, 'index']) ->description(Markdown::line('Returns a **list** of users.')); // Class name that implements Stringable — will be instantiated automatically Route::get('/api/users', [UserController::class, 'index']) ->description(UserIndexDescription::class);
CLI Options Reference
| Option | Alias | Description |
|---|---|---|
--laravel |
— | Bootstrap and parse a Laravel application |
--routes-parser |
-parser |
Fully qualified class name of a custom route parser |
--attributes-in |
-attributes |
Directories to scan for PostmanFormData attributes (repeatable) |
--collection-name |
-name |
Name of the Postman collection (default: postman.json) |
--collection-description |
-description |
Description of the Postman collection |
--base-url |
-url |
Base URL for requests, stored as {{base_url}} variable |
--oauth-token-url |
-oauth-url |
OAuth token endpoint, stored as {{oauth_full_url}} variable |
--route-paths |
-paths |
Filter routes by path patterns (semicolon-separated) |
--route-names |
-names |
Filter routes by name patterns (semicolon-separated) |
--route-methods |
-methods |
Filter routes by HTTP methods (semicolon-separated) |
--route-middlewares |
-middlewares |
Filter routes by middleware (semicolon-separated) |
--output |
— | File path to write the collection to (prints to stdout if omitted) |
--format |
— | Output format: json (default) or pretty-json |
Extending the Package
Custom Route Parser
To support frameworks other than Laravel, implement the RouteParserContract interface:
use Amondar\Postman\Contracts\RouteParserContract; use Amondar\Postman\Route\Route; use Amondar\Postman\Route\RouteCollection; class SymfonyRoutesParser implements RouteParserContract { public function parse(string $rootPath): RouteCollection { $collection = new RouteCollection(); // Parse your framework's routes and convert them foreach ($this->getSymfonyRoutes($rootPath) as $sfRoute) { $collection->push(new Route( name: $sfRoute->getName(), path: $sfRoute->getPath(), methods: $sfRoute->getMethods(), action: null, // or create a RouteAction if applicable middleware: [], )); } return $collection; } }
Use it via CLI:
./vendor/bin/postman export \ --routes-parser="App\\Documentation\\SymfonyRoutesParser" \ --base-url=https://api.example.com
Or programmatically:
$parser = new SymfonyRoutesParser(); $routes = $parser->parse('/path/to/project/can/be/ignored'); $json = Export::from($routes, 'https://api.example.com')->toJson();
Custom Authentication
Create your own authentication type by implementing the AuthenticationContract interface:
use Amondar\Postman\Contracts\AuthenticationContract; class ApiKeyAuth implements AuthenticationContract { public function __construct( public readonly string $headerName = 'X-API-Key', public readonly ?string $value = null, ) {} public function toArray(): array { return [ 'type' => 'apikey', 'apikey' => [ [ 'key' => 'key', 'value' => $this->headerName, 'type' => 'string', ], [ 'key' => 'value', 'value' => $this->value ?? '', 'type' => 'string', ], [ 'key' => 'in', 'value' => 'header', 'type' => 'string', ], ], ]; } }
Then use it as default auth or per-route:
$export = Export::from($routes, $baseUrl) ->useDefaultAuth(new ApiKeyAuth('X-API-Key', 'secret')); // Or in Laravel routes: Route::get('/api/data', [DataController::class, 'index']) ->auth(new ApiKeyAuth());
Custom Form Data Renderable
Implement the Renderable interface to create reusable form data definitions:
use Amondar\Postman\Contracts\Renderable; class PaginationFormData implements Renderable { public function render(): array { return [ 'page' => 1, 'per_page' => 15, 'sort_by' => 'created_at', 'sort_order' => 'desc', ]; } }
Then reference it in the PostmanFormData attribute:
use Amondar\Postman\Attributes\PostmanFormData; class UserController { #[PostmanFormData(PaginationFormData::class)] public function index() { // ... } }
License
Please see LICENSE.md for details.