Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

API Documentation with Scribe for Laravel

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.

  1. 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
  2. Publish config and assets.
    php artisan scribe:install
    This creates config/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.
  3. 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',
    ],
  4. Point Scribe at your API routes. By default it scans routes/api.php. Multi-version APIs often register under Route::prefix('v1') groups — add each prefix explicitly in the routes array or Scribe silently skips endpoints.
    'routes' => [
        [
            'match' => [
                'prefixes' => ['api/*'],
                'domains' => ['*'],
            ],
            'include_middleware' => ['api'],
        ],
    ],
  5. Configure authentication. Without this step, every example request returns 401 and your docs look broken.
    'auth' => [
        'enabled' => true,
        'default' => true,
        'in' => 'bearer',
        'name' => 'Authorization',
        'use_value' => env('SCRIBE_AUTH_KEY'),
        'placeholder' => '{YOUR_AUTH_TOKEN}',
    ],
    Add SCRIBE_AUTH_KEY to .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.

Scribe Setup FlowComposerdev dependencyscribe:installconfig + .scribe/Configureroutes + authGeneratescribe:generateOutput: public/docs/index.html • openapi.yaml • collection.jsonCommit artefacts or serve from CI build
Four-step scribe API documentation setup flow for Laravel 12 and 13 applications

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.

Extraction Priority@response / @bodyParamExplicit overrideForm Request RulesAuto types and constraintsType HintsReflection fallbackGenerated OutputOpenAPI + HTML + PostmanHigher priority wins conflictsMerged into single spec per route
How scribe API documentation merges docblocks, Form Requests, and type hints into final output

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 @response tags with full JSON examples on the controller method.
  • Configure example_models in config/scribe.php to point Scribe at factory-backed model instances.
  • Enable try_it_out with 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.

Docs in CI/CD PipelineGit PushTests Passscribe:generate--force flaggit diff checkfail if staleDeploy with Deployer 7public/docs/ ships as static assetsNo PHP required to serve documentation
Automated scribe API documentation validation before Laravel deployment

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.

CriteriaScribeSwagger-PHPScramble
Source of truthDocblocks + Form Requests + reflectionOpenAPI attributes/annotations onlyCode-first inference, minimal annotations
Laravel awarenessDeep — Form Requests, middleware, ResourcesGeneric PHP, framework-agnosticDeep — types, validation, Resources
Maintenance burdenLow when Form Requests existHigh — duplicate spec beside codeVery low on greenfield APIs
Response control@response tags or example_modelsManual annotation onlyAuto from Eloquent/API Resources
OpenAPI version3.0 primary, 3.1 partialFull 3.1 supportFull 3.1 support
HTML docs built-inYes — themed static siteNo — spec file onlyYes — minimal theme
Best fitExisting Laravel apps with Form RequestsCross-framework OpenAPI shopsNew 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.

Which Doc Tool?Existing Laravel API?YesNoForm Requests?Use ScrambleScribeSwagger-PHPReuses validationPure OpenAPIZero-configNeed auditable @response tags? Pick Scribe
Decision framework for scribe API documentation versus Scramble and Swagger-PHP in 2026

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 routes array in config. Verify prefix patterns include api/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 @response tags or configure example_models. Without sample data, Resources return empty nested objects.
  • 401 on all example requests. Check auth.use_value resolves 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:sanctum still appear in docs but show as authenticated. Routes excluded by exclude_middleware misconfiguration may vanish entirely — audit that array carefully.
  • Versioned APIs documented twice. If v1 and v2 share controllers, use separate route groups in config or @hideFromAPIDocumentation on deprecated endpoints. See Laravel API versioning strategy for prefix patterns that Scribe handles cleanly.
  • Stale cache after docblock edits. Always run scribe:generate --force after 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 --force in CI and fail the pipeline when public/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_URL correctly 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

Scribe is a Laravel package that generates interactive API docs from code annotations, config files, and response examples without requiring external services or manual markdown maintenance.

Scribe is free and open-source under MIT license. Paid tiers exist only for hosted cloud documentation; self-hosted static HTML generation remains completely free for unlimited commercial use.

Scribe integrates natively with Laravel routing and validation rules, reducing boilerplate compared to generic OpenAPI tools, though Swagger offers broader language support outside the PHP ecosystem.

Run composer require --dev knuckleswtf/scribe then php artisan scribe:install. This publishes the config file to config/scribe.php and creates a base documentation route at /docs. Ensure your application runs PHP 8.2 or higher as required by Laravel 12. The installation wizard asks whether you want static HTML, Postman collection, or OpenAPI spec output formats.

Yes, Scribe parses Form Request classes referenced in controller method signatures and extracts validation rules, field types, and custom messages automatically. This eliminates duplicating validation logic in docblocks. If detection fails, verify the Form Request type-hint matches exactly and that rules are defined in the rules() method rather than inline in controllers. Complex nested array validation may still require manual annotation overrides using @bodyParam tags.

Configure auth strategies in config/scribe.php under the auth key. For Sanctum token-based APIs common in Laravel projects I have built, set type to bearer and provide sample tokens in the example section. Scribe uses these values when generating request examples but never exposes real credentials. You can also define per-endpoint authentication requirements using @authenticated annotations in controller docblocks to override global defaults for specific routes like public health checks.

Scribe fully supports resource controllers and resolves route model binding parameters automatically. It detects Eloquent model types from type-hints and generates appropriate path parameter documentation including expected formats. When working on legal-tech portals with complex nested resources, I have found that explicitly naming parameters in route definitions improves clarity over relying solely on implicit binding names. Use @urlParam annotations if auto-detection produces incorrect descriptions or misses custom key fields.

Use Scribe response strategies to return fake data during generation. The faker strategy generates realistic payloads based on return types, while the database strategy queries actual records safely. For payment integrations like eSewa or Khalti where live calls are inappropriate during doc generation, configure custom response factories returning sanitized sample responses. Define these in config/scribe.php under response_strategies. This keeps documentation accurate without triggering real transactions or exposing sensitive third-party sandbox credentials.

Scribe ships with default themes but supports full customization through Blade views published via php artisan vendor:publish --tag=scribe-views. Modify resources/views/vendor/scribe/ to adjust navigation, styling, or content structure. On client projects requiring branded documentation portals, I typically extend the base template rather than replacing it entirely to preserve upgrade compatibility. Custom CSS can be added through the assets configuration key. Remember that major version upgrades may introduce breaking view changes, so pin your Scribe version in composer.json.

Group endpoints by version using route prefixes or dedicated documentation groups in config/scribe.php. Define separate group configurations pointing to different route patterns like api/v1/ and api/v2/. Each group generates its own documentation section with independent settings. For REST APIs serving multiple consumer versions simultaneously, this approach maintains clear separation without mixing deprecated and current endpoints. Version-specific authentication or base URL differences can be configured per group, which proves essential when maintaining legacy integrations alongside newer implementations.

Empty responses usually indicate failed response strategy execution or missing return type declarations. Check storage/logs/laravel.log for exceptions during php artisan scribe:generate. Common causes include unresolvable dependencies in controller constructors, missing environment variables needed for faker seeding, or response strategies attempting database queries against empty tables. Add @response annotations with explicit JSON payloads as fallbacks for problematic endpoints. Verify that test databases contain seed data if using the database response strategy, as Scribe does not run migrations automatically during generation.

Add php artisan scribe:generate to your GitLab CI pipeline before the Deployer 7 deploy task. Commit generated docs to the repository or pass them as artifacts to the deployment stage. On sister sites sharing deployment infrastructure, I generate documentation during the build phase and include it in the release artifact bundle. Configure Scribe to output static HTML to public/docs so Nginx serves it directly without PHP overhead. Cache Composer dependencies and Scribe's internal cache between pipeline runs to reduce generation time significantly.

Yes, enable postman collection output in config/scribe.php by adding postman to the output_formats array. Scribe generates a complete Postman collection JSON file with environments, authentication presets, and example requests matching your HTML documentation. Import this directly into Postman for immediate testing. Keep both outputs synchronized by running single generation commands rather than separate processes. For teams collaborating across frontend and backend roles, providing both formats reduces onboarding friction and ensures testers work against current endpoint specifications without manual translation errors.

Use @bodyParam annotations with type file for upload fields. Specify accepted MIME types and size limits in the description since Scribe cannot infer these from validation rules alone. For multipart form submissions combining files with JSON metadata, declare each field separately rather than nesting. In production eCommerce systems handling product image uploads, I have found that explicitly documenting maximum dimensions and format restrictions prevents client confusion. Test generation with actual sample files placed in storage/app/scribe-uploads to verify examples render correctly without broken references.

Never expose internal endpoints, admin routes, or debug information in public documentation. Use group filtering in config/scribe.php to exclude sensitive route patterns. Strip real API keys, tokens, and customer data from all examples even when using development databases. Review generated output manually before deployment since automated strategies may leak unintended details through error messages or database records. Host documentation behind authentication for internal APIs using Laravel middleware applied to the docs route. Regularly audit published docs as part of security reviews since endpoint changes can inadvertently expose new attack surface information.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: