
August 14, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
Your REST API works in production, but integrators still ping you for field names, auth headers, and error shapes. Scribe API documentation closes that gap by generating OpenAPI 3.0 specs, browsable HTML, and Postman collections straight from Laravel 12 or 13 code. You stop maintaining a separate YAML file that drifts every sprint. The docs stay tied to the same Form Requests and route definitions your app already validates against.
How do you install and configure Scribe for Laravel API documentation?
Start by treating Scribe as a build-time tool. I install it with Composer as a dev dependency on every Laravel API I maintain. It never ships to production PHP-FPM pools. Before you add documentation tooling, confirm your endpoints follow sensible patterns from this Laravel API best practices guide — Scribe documents what you already built, it does not fix bad architecture.
- Install the package. Scribe 4.x supports Laravel 12 and 13 on PHP 8.2 or higher. Laravel 13 requires PHP 8.3 minimum.
composer require --dev knuckleswtf/scribe:^4.0 - Publish config and assets.
This createsphp artisan scribe:installconfig/scribe.php, a.scribe/directory for overrides, and default Blade views. Review the config before your first generate run — defaults rarely match multi-prefix APIs out of the box. - Set output formats. Most teams need HTML for humans and OpenAPI for tooling. Postman collections help QA and mobile developers.
'type' => 'static', 'output_path' => 'public/docs', 'output_formats' => [ 'html', 'openapi', 'postman', ], - Point Scribe at your API routes. By default it scans
routes/api.php. Multi-version APIs often register underRoute::prefix('v1')groups — add each prefix explicitly in theroutesarray or Scribe silently skips endpoints.'routes' => [ [ 'match' => [ 'prefixes' => ['api/*'], 'domains' => ['*'], ], 'include_middleware' => ['api'], ], ], - Configure authentication. Without this step, every example request returns 401 and your docs look broken.
Add'auth' => [ 'enabled' => true, 'default' => true, 'in' => 'bearer', 'name' => 'Authorization', 'use_value' => env('SCRIBE_AUTH_KEY'), 'placeholder' => '{YOUR_AUTH_TOKEN}', ],SCRIBE_AUTH_KEYto.env.example, never commit a real token. For Passport versus Sanctum decisions, see the Laravel Sanctum vs Passport comparison.
A common mistake on client projects: developers hardcode a personal Sanctum token in config and push it to Git. Pull tokens from environment variables only. Rotate them when team members leave.
How does Scribe extract endpoint details from Laravel controllers and Form Requests?
Scribe merges three sources in priority order. Explicit docblock tags win. Form Request rules come next. PHP type hints and reflection fill gaps last. Knowing this hierarchy saves hours when generated output does not match what you expected.
Form Requests as the primary documentation source
If your controllers already use Form Requests — and they should, per modern Laravel architecture best practices — Scribe reads validation rules without extra annotations. A rule like 'email' => 'required|email|max:255' becomes a documented string field marked required with a max-length constraint.
// app/Http/Requests/StoreLeadRequest.php
public function rules(): array
{
return [
'full_name' => ['required', 'string', 'max:100'],
'email' => ['required', 'email'],
'service_id' => ['required', 'exists:services,id'],
'message' => ['nullable', 'string', 'max:2000'],
];
} Custom validation messages in messages() appear in the generated parameter descriptions. That helps integrators understand rejection reasons before they hit a 422 response. For advanced validation patterns, see Laravel Form Request validation patterns.
Docblock tags for responses and edge cases
Live database calls during generation are slow and brittle. Use @response tags with inline JSON for canonical shapes. These override runtime responses and stay consistent regardless of seed data.
/**
* Create a new legal service lead.
*
* @group Leads
* @authenticated
*
* @bodyParam full_name string required Max 100 chars. Example: Ram Thapa
* @bodyParam email email required Example: ram@example.com
* @bodyParam service_id integer required Example: 5
*
* @response 201 scenario="Created" {"id": 42, "status": "pending"}
* @response 422 scenario="Validation error" {"message": "...", "errors": {"email": ["..."]}}
* @response 429 scenario="Rate limited" {"message": "Too Many Attempts."}
*/
public function store(StoreLeadRequest $request): JsonResponse On legal-tech portals I have shipped, this pattern removed an entire class of bugs. Documentation showed old field names after a migration renamed columns. Nobody updated a hand-written YAML spec. Regeneration pulled from the same rules the app validates against.
Nested arrays and rules Scribe struggles with
Scribe 4.x handles dot-notation rules like 'items.*.price' => 'required|numeric' reasonably well. It struggles with conditional rules using Rule::when() or deeply nested JSON payloads. Add explicit @bodyParam tags for those structures instead of relying on inference alone.
How do you document API Resources, groups, and authentication with Scribe?
Form Requests cover input. API Resources shape output. Scribe needs help with Resources because it cannot always infer nested JSON from Eloquent models alone.
API Resources and response transformers
When you return LeadResource::make($lead), Scribe may produce empty objects if it lacks sample data. Three fixes work in practice:
- Add
@responsetags with full JSON examples on the controller method. - Configure
example_modelsinconfig/scribe.phpto point Scribe at factory-backed model instances. - Enable
try_it_outwith a seeded database during generation — slower, but accurate for complex nested Resources.
Compare Resource patterns in the Laravel API Resources vs Fractal guide. Scribe understands Laravel API Resources natively. Fractal transformers need more manual docblock work.
Grouping endpoints for readable navigation
Use @group tags in controller docblocks to organise endpoints by domain — Leads, Documents, Payments. Scribe renders these as sidebar sections in HTML output. Without groups, a 60-endpoint API becomes an unreadable flat list.
/**
* @group Client Documents
*/
class DocumentController extends Controller Documenting rate limits and error headers
Laravel's ThrottleRequests middleware returns X-RateLimit-Limit and X-RateLimit-Remaining headers. Scribe does not auto-document these. Add a @header tag or describe limits in your API introduction block inside config/scribe.php under intro_text. Cross-reference your throttle config with the API rate limiting guide so documented limits match runtime behaviour.
For a client portal like Mijar Law Associates, we documented Sanctum bearer auth, per-role endpoint access, and 429 responses explicitly. Integrators stopped guessing whether 403 meant bad token or missing permission.
How do you generate OpenAPI specs and integrate Scribe into CI/CD pipelines?
Documentation that is not regenerated on every deploy is already stale. Treat Scribe output as a build artefact. On sister sites sharing Deployer 7 + GitLab CI, I run generation in the validate stage before deployment proceeds.
Generation commands and cache behaviour
# Standard generation
php artisan scribe:generate
# Ignore cached .scribe/endpoints.cache
php artisan scribe:generate --force
# OpenAPI only — faster for spec validation in CI
php artisan scribe:generate --format openapi The endpoint cache speeds local iteration. It does not always invalidate when you change Form Requests. Run --force after validation rule changes. Commit the cache only if your team agrees — many teams gitignore .scribe/endpoints.cache and always force-regenerate in CI.
GitLab CI documentation gate
docs-check:
stage: validate
image: php:8.4-cli
script:
- composer install --no-interaction --prefer-dist
- cp .env.example .env
- php artisan key:generate
- php artisan scribe:generate --force
- git diff --exit-code public/docs
artifacts:
paths:
- public/docs/openapi.yaml
expire_in: 1 week The git diff --exit-code check fails the pipeline when a developer updates routes but forgets to regenerate. That single gate prevented more stale-doc incidents on my projects than any PR checklist. See the full GitLab CI pipeline for Laravel walkthrough for stage ordering context.
GitHub Actions alternative
- name: Verify API docs are current
run: |
php artisan scribe:generate --force
git diff --exit-code public/docs Serving and securing generated docs
Scribe writes static files to public/docs/ by default. Serve them as plain static assets through Nginx or Apache — never route through PHP-FPM. For internal APIs, protect the path with IP allowlists or basic auth at the web-server layer. Public exposure of endpoint structure invites reconnaissance. I have seen this flagged in security audits twice.
Pair the OpenAPI spec with ReDoc or Swagger UI if you want an interactive explorer beyond Scribe's built-in HTML theme. Validate spec syntax with the JSON formatter tool when debugging malformed response examples.
What are the practical differences between Scribe, Swagger-PHP, and Scramble for Laravel?
Tool choice depends on team workflow and codebase age — not feature checklists alone. All three produce OpenAPI output. They differ in where you maintain the source of truth.
| Criteria | Scribe | Swagger-PHP | Scramble |
|---|---|---|---|
| Source of truth | Docblocks + Form Requests + reflection | OpenAPI attributes/annotations only | Code-first inference, minimal annotations |
| Laravel awareness | Deep — Form Requests, middleware, Resources | Generic PHP, framework-agnostic | Deep — types, validation, Resources |
| Maintenance burden | Low when Form Requests exist | High — duplicate spec beside code | Very low on greenfield APIs |
| Response control | @response tags or example_models | Manual annotation only | Auto from Eloquent/API Resources |
| OpenAPI version | 3.0 primary, 3.1 partial | Full 3.1 support | Full 3.1 support |
| HTML docs built-in | Yes — themed static site | No — spec file only | Yes — minimal theme |
| Best fit | Existing Laravel apps with Form Requests | Cross-framework OpenAPI shops | New Laravel 13 APIs, zero overhead |
I default to Scribe on existing Laravel applications because Form Requests are usually already in place. Adding @response tags costs far less than retrofitting Swagger attributes across hundreds of endpoints. Scramble wins on greenfield Laravel 13 projects where inference covers 90% of cases. Swagger-PHP suits teams standardising on pure OpenAPI across PHP and non-PHP services. Read the OpenAPI 3.1 specification guide if your consumers require 3.1-only features like webhooks or JSON Schema dialect updates.
How do you troubleshoot missing endpoints and incorrect response examples in Scribe?
Even with correct configuration, Scribe misses routes or produces wrong examples. These are the recurring issues I fix on production Laravel APIs.
- Endpoints missing entirely. Scribe only documents routes matching the
routesarray in config. Verify prefix patterns includeapi/v1/*if you version URLs. Routes behind inactive feature flags will not appear unless the flag is on during generation. - Wrong response shape with API Resources. Add
@responsetags or configureexample_models. Without sample data, Resources return empty nested objects. - 401 on all example requests. Check
auth.use_valueresolves to a valid Sanctum token. Expired tokens are the most common cause. Generate a fresh token via tinker before CI runs. - Middleware hiding routes. Routes using
auth:sanctumstill appear in docs but show as authenticated. Routes excluded byexclude_middlewaremisconfiguration may vanish entirely — audit that array carefully. - Versioned APIs documented twice. If v1 and v2 share controllers, use separate route groups in config or
@hideFromAPIDocumentationon deprecated endpoints. See Laravel API versioning strategy for prefix patterns that Scribe handles cleanly. - Stale cache after docblock edits. Always run
scribe:generate --forceafter changing annotations or Form Requests.
For teams building on RESTful APIs with Laravel, regenerate docs locally before pushing. Add a pre-commit hook or make it muscle memory. Validate generated specs against real requests using Postman and Newman in CI.
Customising output with the .scribe directory
The .scribe/ folder holds Markdown intro sections, custom logo paths, and endpoint overrides. Edit .scribe/intro.md for authentication overview, base URLs per environment, and support contact details. Override individual endpoints in .scribe/endpoints/ when auto-extraction fails for one problematic route without touching global config.
Production gotcha: APP_URL during generation
Scribe embeds APP_URL into example request URLs. If CI runs with APP_URL=http://localhost, your published docs show localhost links. Set the correct staging or production URL in the CI environment before generation. I hit this on a Deployer pipeline where the docs stage inherited the wrong env file — integrators received unusable try-it-out URLs until we fixed the variable.
Official reference: the Scribe for Laravel documentation covers strategies, response calls, and customising views. Laravel's Sanctum documentation explains token generation for auth examples.
Key Takeaways
- Install Scribe as a dev dependency and configure route prefixes, auth, and output formats before the first generate run.
- Anchor documentation to Form Request validation rules — add @response tags only where Resources or nested payloads need explicit examples.
- Run
scribe:generate --forcein CI and fail the pipeline whenpublic/docs/differs from committed output. - Protect generated docs at the web-server layer for internal APIs; never expose endpoint maps without access control.
- Set
APP_URLcorrectly in CI so example request URLs point at real environments, not localhost. - Choose Scribe over Scramble when you need auditable @response tags on client-facing APIs with strict field requirements.
People Also Ask
Does Scribe support Laravel 13 and PHP 8.5?
Scribe 4.x supports Laravel 12 and 13 on PHP 8.2 or higher. Laravel 13 requires PHP 8.3 minimum. PHP 8.5 runs fine on supported Laravel versions. Check the package release notes before upgrading major framework versions — annotation tag parsing occasionally shifts between Scribe minor releases.
Can Scribe generate documentation without hitting the database?
Yes. Use static @response and @bodyParam tags to avoid response calls entirely. This is the recommended approach for CI environments without seeded databases. Enable response_calls only when you accept slower generation and need live Eloquent output.
Where should generated Scribe docs be hosted?
Commit static output to public/docs/ and serve it as plain files through Nginx or Apache. Alternative: upload the OpenAPI spec to an internal portal or S3 bucket. Do not route doc requests through PHP-FPM — they add latency and attack surface for zero benefit.
How does Scribe compare to manually writing OpenAPI YAML?
Manual YAML drifts from code within weeks on active projects. Scribe reads the same Form Requests your app validates against, so parameter types stay aligned. You still add @response tags for output shapes, but input documentation largely maintains itself.
Ship accurate scribe API documentation on every deploy
Reliable scribe API documentation comes from treating docs as a build artefact, not an afterthought. Install Scribe as a dev dependency, tie extraction to Form Requests you already write, pin response examples with @response tags, and enforce regeneration in CI before Deployer swaps the release symlink. That workflow has kept documentation accurate across legal-tech portals, eCommerce APIs, and booking systems I maintain in production.
If your team needs Scribe configured on an existing Laravel API, OpenAPI wired into a consumer SDK, or a full documentation pipeline integrated with GitLab CI, explore API development services or testing and optimization support. For a quick architecture review of your current setup, contact us. You can also reach out directly with your route file and config — I review those before recommending Scribe versus Scramble for your specific codebase.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

