Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Scribe Laravel Package

knuckleswtf/scribe

Scribe generates human-friendly API documentation from your Laravel codebase. It extracts request parameters from FormRequests or validation rules, calls endpoints for sample responses, and outputs a single-page HTML doc with a Try It Out tester, plus Postman collections and OpenAPI specs.

View on GitHub
Deep Wiki
Context7

Getting Started

Scribe is a dev-only dependency that turns your existing Laravel routes, FormRequests and API Resources into documentation. You rarely write docs by hand — you annotate, then generate.

composer require --dev knuckleswtf/scribe
php artisan scribe:install     # publishes config/scribe.php and picks laravel/laravel mode
php artisan scribe:generate    # writes public/docs/{index.html,collection.json,openapi.yaml}

Then open http://your-app.test/docs (or public/docs/index.html) — the default theme includes "Try It Out" against your configured base URL.

Look here first, in this order:

  1. config/scribe.php — read it top to bottom once. The keys that matter day-to-day are type, routes.include/exclude, auth, example_languages, groups, strategies, and openapi.version.
  2. config/scribe.php → routes — by default Scribe documents everything in routes/api.php. Tighten this immediately so internal/admin routes don't leak into public docs:
'routes' => [
    'include' => [ 'api/*' ],
    'exclude' => [ 'api/internal/*', 'api/health' ],
],
  1. app/Http/Requests/* and app/Http/Resources/* — Scribe reads these automatically, so most endpoints need zero annotations.

First use case. Take one controller action, add a group and let validation rules do the parameter docs:

use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\UrlParam;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;

#[Group('Posts', 'Create, read, update and delete posts')]
class PostController extends Controller
{
    #[UrlParam('post', 'The post ID', example: 1)]
    #[ResponseFromApiResource(PostResource::class, Post::class, collection: false)]
    public function show(ShowPostRequest $request, Post $post) { /* ... */ }
}

Run php artisan scribe:generate and that endpoint now has a description derived from your docblocks, a request body derived from ShowPostRequest::rules(), a sample response derived from PostResource, and code samples in every language listed in example_languages.

Implementation Patterns

Metadata-first, annotation-second. Scribe's extraction strategies are ordered: FormRequests and Validator::make() rules give you body/query params for free; ApiResource/Transformer classes give you response schemas. Only reach for #[BodyParam], #[QueryParam], #[HeaderParam], #[UrlParam] and #[Response] when the inference is wrong or the data doesn't live in code (webhooks, third-party payloads). Over-annotating is the most common way docs drift out of sync with reality.

Group by resource, control order in config. Use #[Group('Posts', 'description')] (or @group in the docblock for older codebases) consistently, then fix navigation order once:

'groups' => [
    'order' => ['Authentication', 'Users', 'Posts', 'Webhooks'],
    'default' => 'Endpoints',
],

Pick a response strategy per endpoint, not per project.

  • Live call (response_calls.enabled = true) — best fidelity, but hits your real app and DB.
  • #[ResponseFromApiResource(PostResource::class, Post::class)] with a factory — deterministic, readable, no HTTP round trip.
  • #[Response(['data' => ['id' => 1]])] — hand-written JSON for the awkward 10%.

A pragmatic mix: live calls for read-only GETs, resource-derived samples for writes.

Wire outputs to their consumers. Generate all three formats and let each team use what they need — HTML for PMs/support, openapi.yaml for frontend codegen (orval, openapi-generator, typed clients), collection.json for QA in Postman. Disable what nobody consumes:

'postman' => ['enabled' => true, 'overrides' => ['info.version' => '1.0.0']],
'openapi' => ['enabled' => true, 'version' => '3.1.0'],

Run it in CI, not on someone's laptop. Regenerate on every deploy so docs can't rot. Because response calls need a working DB and app, give it an isolated environment:

- run: php artisan migrate --seed
- run: php artisan scribe:generate --force
- uses: actions/upload-artifact@v4
  with: { name: api-docs, path: public/docs }

If your DB is unavailable in CI, generate with response calls off and rely on #[ResponseFromApiResource] — php artisan scribe:generate --help lists the current flag names, which have shifted between majors.

Multiple audiences, multiple configs. For a public API and an internal/partner API, keep two config files (e.g. scribe.php, scribe_internal.php) and generate each with --config=; the docs cover the exact flag for your version. This beats one sprawling doc with everything hidden behind #[hideFromAPIDocumentation].

Use the extension points instead of forking. strategies.* lets you drop in your own extractor classes, and recent 5.x releases expose an afterExtracting hook where you can enrich endpoint data (add a deprecated note, rename fields, inject a shared error envelope). base_url even supports Blade syntax, which is handy for multi-tenant doc builds. Custom response content types (application/problem+json) are supported — configure them in response_calls/#[Response] rather than letting Scribe assume JSON.

Auth and Try It Out. Set auth.enabled, auth.in, auth.name and auth.use_value: false so callers see a placeholder token instead of a real one. If your docs sit behind a session-authenticated app, enable try_it_out.use_csrf and point csrf_url at /sanctum/csrf-cookie or /csrf-token.

Gotchas and Tips

  • Response calls are not side-effect-free. POST /posts during generation really creates a Post. Do not point scribe:generate at production. Always list your connections in database_connections_to_transact so each response call is rolled back — and prefer a throwaway SQLite/in-memory DB for docs builds.
  • Factories fire model events. #[ResponseFromApiResource] with Model::factory()->create() can trigger observers, notifications, and search-index pushes. 5.8 added an opt-in to suppress model events during sample generation — turn it on in any app with heavy observers.
  • Empty sample response? Check auth, not Scribe. Unauthenticated response calls yield 401/403 bodies that look like "broken docs". Either configure auth for response calls or mark those endpoints with hand-written #[Response]. Also check storage/logs/laravel.log — Scribe swallows many extraction errors and moves on.
  • Route model binding shows up as a required integer. Override with #[UrlParam('post', 'The post ID', example: 1)] or bind on a UUID/slug, otherwise your frontend team will send IDs where you expect slugs.
  • Choose your OpenAPI version once. 3.0.3 (default) uses nullable: true; 3.1.0 uses JSON-Schema type: ["string","null"]. Codegen tools differ wildly in what they accept — pick early and pin it, because switching later changes every generated schema.
  • Stay current within 5.x. A large share of historical papercuts are fixed in point releases: array-of-objects schemas, wrapped API Resources being honored (5.7), Postman query keys being double-URL-encoded (5.11), nested BelongsTo loading (5.11), HTML special characters breaking the Try It Out JSON panel (5.7). If something looks wrong, check the changelog before debugging your annotations.
  • Regeneration overwrites public/docs and vendor assets. Any hand-edited HTML is lost. Publish and edit the Blade views under resources/views/vendor/scribe instead; do the same for intro_text (Markdown) and custom UI partials.
  • #[Deprecated] is documentation-only. It adds a badge and OpenAPI deprecation flag; it does not affect routing or emit headers. Pair it with a real Sunset header if you actually want consumers to notice.
  • Regenerate after route or config changes. Route caching, new middleware, moved controllers, and toggled feature flags all change what Scribe extracts. A stale openapi.yaml in CI is worse than none — always generate rather than commit-and-forget.
  • Keep secrets out of examples. example values, intro_text, seeded factory data, and base_url all end up in a static file that's often deployed publicly. Use fake hosts, fake tokens, and fake emails — never real customer records pulled from a seeded dev DB.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
wandesnet/mercadopago-laravel
tsfcorp/email
larafony/core
laravel-enso/currencies
tartan/laravel-online-payment
gam/laravel-sat-catalogs
cloudcreativity/laravel-stripe
abdulelahragih/querybuilder
wedevbr/celcoin-laravel
surazdott/nepali-date
devsquad-cockpit/laravel
coding-socks/laravel-upload-handler
artisanbarista/laravel-shield
shopwwi/laravel-cache
instance-code/repository
flooris/prestashop-models
lab2view/laravel-generator
lenderspender/laravel-wizard
joecampo/laravel-notification-basecamp
square1/laravel-collection-rolling-average