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

Json Schema Extractor Laravel Package

zeusi/json-schema-extractor

Generate JSON Schema definitions directly from your Laravel/PHP code. This package extracts structured schemas for models, validation rules, and data shapes, making it easy to document APIs, power client-side validation, and keep schema definitions in sync with your application.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and pin.

composer require zeusi/json-schema-extractor --dev

The package sits at 4 stars with zero dependents, so treat it as an internal tool, not infrastructure: commit the exact version in composer.lock, and read the source before you build a workflow on it. It is small enough that src/ is the real documentation.

2. Read in this order — this is where to look first.

Where What you learn
README.md Only the intended entry point + one example. Often behind the actual API.
src/ The extractor/visitor classes. This tells you which metadata sources are actually supported.
tests/ fixtures The authoritative spec. Fixture DTOs + expected .json output show exactly which attributes, docblocks, and types map to which keywords.
composer.json PHP version floor, whether it depends on doctrine/annotations, symfony/serializer, nikic/php-parser, or is standalone reflection-only.

Copy a fixture DTO into your own test suite and generate a schema from it. If that round-trips, the package works for you; if not, you have found the boundary before writing 40 DTOs against it.

3. First use case: one DTO, one schema, one validated payload.

// Exact facade name comes from the README; the shape is what matters.
$schema = (new SchemaExtractor())
    ->addMetadataSource(new AttributeMetadataSource())  // #[Required], #[Format], ...
    ->extract(CreateUserRequest::class);

file_put_contents(
    __DIR__.'/../resources/schema/create-user-request.json',
    json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);

Then validate a real request body against it with an off-the-shelf validator (opis/json-schema or justinrainbow/json-schema). The extractor only produces schemas — it does not validate. Wiring a validator in on day one is the fastest way to discover which keywords the extractor emits incorrectly.

Implementation Patterns

Pattern A — Per-DTO schema, generated at build time

Never generate schemas in a request cycle. Add an Artisan command or a composer dump-schema script that walks a directory of DTOs and writes one file per class, then commit the output. The schemas become reviewable artifacts in the diff, and a reviewer can see when a contract change is unintentional.

foreach ((new Finder())->files()->in('app/Http/Dto')->name('*.php') as $file) {
    $class = App\Dto\ClassNameResolver::fromFile($file);
    $this->write($class, $extractor->extract($class));
}

Pattern B — Root DTO with $ref components (OpenAPI bridge)

Model your API as a graph: one root request/response DTO, nested DTOs as properties. Let the extractor hoist nested classes into definitions/$defs and reference them. This is the only sane way to keep schemas DRY across endpoints.

$components = [];
foreach ($dtoClasses as $class) {
    $components[$shortName($class)] = $extractor->extract($class);
}

$openApi = [
    'openapi' => '3.1.0',
    'components' => ['schemas' => $components],
    'paths' => [/* $ref: '#/components/schemas/CreateUserRequest' */],
];

If the extractor inlines everything instead of emitting $ref, either post-process the output (a recursive walk replacing duplicate sub-schemas) or accept one giant schema per endpoint. Do not hand-maintain a hybrid.

Pattern C — Schema as a test oracle

Use the generated schema to validate your own factories and fixtures. This catches the classic DTO drift: you added a required field, forgot the factory, and only production noticed.

public function test_create_user_dto_matches_contract(): void
{
    $schema = json_decode(file_get_contents(__DIR__.'/../../resources/schema/create-user-request.json'));
    $payload = CreateUserRequest::factory()->raw();

    $this->assertTrue($validator->validate($payload, $schema));
}

Pattern D — Request validation middleware (Laravel)

// app/Http/Middleware/ValidateJsonSchema.php
public function handle($request, Closure $next)
{
    $schemaFile = resource_path('schema/'.Str::kebab(class_basename($request->route()->getActionName())).'.json');

    if (file_exists($schemaFile)) {
        $errors = $this->validator->validate(
            json_decode($request->getContent()),
            json_decode(file_get_contents($schemaFile))
        );
        if ($errors) {
            throw ValidationException::withMessages(['*' => $errors]);
        }
    }

    return $next($request);
}

This is a good fit when your frontend consumes the same schemas for form generation — one source of truth, both directions.

Pattern E — Contract-drift CI gate

php artisan schema:dump
git diff --exit-code resources/schema/ || (echo "JSON Schema out of date, run schema:dump"; exit 1)

Cheap, and it catches the exact failure mode of a low-traffic package: someone updates a DTO, the schema silently diverges, and no dependents ever file the bug.

Pattern F — Metadata layering

The "leverage existing metadata" pitch means your validation attributes and PHPDoc are the schema source. Keep that discipline: put constraints in attributes/annotations, not in a parallel hand-written schema file. If a constraint has no extractor support, add a custom metadata source (the modular part) rather than patching the output JSON by hand.

Gotchas and Tips

Treat the API as unstable until proven otherwise. Version 2026-08-08 on a 4-star, 0-dependent package means no third party has validated the public surface. Pin it, wrap it behind your own thin SchemaGenerator interface, and keep the extractor out of your domain code so a fork or swap is a one-file change.

Read the fixtures before trusting the output. The single highest-value hour you can spend is diffing a fixture DTO against its expected JSON. That answers, definitively: does a nullable property become ["string","null"] or a nullable: true sibling? Does a DateTimeImmutable become format: date-time or a bare string? These decisions are load-bearing for every consumer.

Draft version mismatch is the #1 silent failure. The extractor emits a specific $schema draft; your validator defaults to draft-07. A schema that looks fine but uses 2020-12 keywords ($defs, prefixItems, unevaluatedProperties) will validate nothing under draft-07 and pass everything under additionalProperties: true — a false sense of safety. Pin the draft explicitly on both sides.

Required is inferred, not declared — verify it. If the extractor derives required from constructor promotion (all promoted params with no default) rather than from your validation metadata, you will get surprises with nullable-but-present fields. Overlay required from your actual validator constraints when they disagree, and add a test asserting the two agree.

Collections need explicit generic hints. Reflection cannot see Foo[]. Anything beyond array → {type: array, items: {}} typically needs a PHPDoc generic (@var list<Foo>) or an attribute. Grep your DTOs for array typed properties; each one is a schema hole.

Recursion and cycles. Self-referencing DTOs (tree nodes, comments with replies) explode into infinite recursion unless the extractor detects cycles and emits $ref. Test one deliberately before you have a stack overflow in CI.

Inheritance and traits. Base-class properties and trait properties are the most commonly skipped reflection targets. Write one fixture DTO extending an abstract base and confirm the parent's fields appear.

Private/protected properties and readonly. Reflection-based extractors frequently read only public properties. PHP 8.1 readonly and promoted constructor properties are worth an explicit check — some reflection paths skip the constructor's promoted params.

Enums. Backed enums should map to type: string, enum: [...]. If they map to a plain string, you have lost the single best validation constraint the generator can produce; consider a custom extractor extension.

Generate at build, cache at runtime. Schema generation is reflection-heavy and never belongs in a request handler. Emit to disk in CI, load from disk at runtime, and let OPcache handle the rest.

Debugging checklist when a schema is wrong, in order:

  1. json_encode the extracted array — is the field missing entirely, or present and wrong?
  2. Is the class autoloadable in the CLI context where you run generation? (class_exists in the command.)
  3. Does the property have a PHPDoc block with a resolvable FQCN? Unresolved use aliases resolve to a broken $ref.
  4. Which metadata source won? If attributes and docblocks both specify a type, log the resolution order — many extractors resolve in registration order, not precedence order.
  5. Diff against a passing fixture. The package's own tests are your reference implementation.

Extension points are the escape hatch, not the plan. The modular design likely lets you register custom metadata sources/extractors. Use it for enums, UUIDs, or decimal money types — but keep each extension in your own package under test, since upstream may rename the interface between releases.

Fall back gracefully. Ship a hand-written schema for the handful of DTOs the extractor handles badly, generated ones for the rest, and one test asserting both validate the same fixtures. Hand-writing 100% of your schemas is the outcome you are trying to avoid; hand-writing 5% is a reasonable price for not fighting a small package.

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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log