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

Eloquent Json Schema Laravel Package

folklore/eloquent-json-schema

Generate JSON Schema definitions directly from your Eloquent models. This package inspects attributes, casts, and relationships to build accurate schemas, making it easy to document APIs, validate payloads, and generate typed clients.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and orient

composer require folklore/eloquent-json-schema

Then read, in this order, before writing a single line of app code:

  1. The README usage example — it will be short. Copy it verbatim into a scratch model.
  2. src/ — find the single cast class and any schema contract or interface it type-hints against. That pair is the package; everything else is glue.
  3. composer.json — check illuminate/* and PHP constraints, and note which JSON Schema validator it pulls in (justinrainbow/json-schema, opis/json-schema, etc.). That validator, not this package, determines draft support and error semantics.
  4. git log and the open-issues page — confirm the repo is dormant, not archived, and whether anyone triages PRs.

First use case: a schema-typed attribute

The realistic day-one move is a single model with a json column that stores a schema document, cast to a typed object:

// database/migrations/...
$table->json('payload_schema')->nullable();

// app/Models/FormDefinition.php
protected $casts = [
    'payload_schema' => \Folklore\EloquentJsonSchema\Casts\JsonSchema::class,
];

Persist a document that conforms to JSON Schema (draft per whatever the bundled validator supports), retrieve it as the cast's typed object, and confirm round-trip behavior through toArray() and toJson() before going further.

Where to look when it breaks

The failure surface is tiny: the cast class, the validator it delegates to, and the schema draft you chose. When something throws on find() or get(), the stack trace will name the validator — start there, not in the package. The package's only job is bridging Eloquent's CastsAttributes to a validator call.

First bounded goal

Pick one non-critical model, one schema fixture (a deliberately valid document), one invalid fixture, and write a test asserting what happens on each. That answers — empirically, in under an hour — the questions that no amount of README reading will: does the cast validate on read, fail open or closed, and survive serialization.

Implementation Patterns

Pattern: schema-as-data on the model

The package's sweet spot is letting a model own its schema as a value, so it hydrates the same way every other attribute does. Keep the schema in its own column rather than embedding it inside the payload:

// Good: schema is queryable, versionable, cachable independently
$table->json('schema');
$table->json('payload');

This makes schema changes auditable (diff rows, not code) and lets you compile/cache the schema object without touching payload reads.

Pattern: wrap the cast behind an internal contract

Do not let the package type leak into controllers, jobs, or resources. Define your own interface and have the cast satisfy it, or write a thin adapter:

interface SchemaDocument
{
    public function toJson(): string;       // canonical schema string
    public function toArray(): array;       // decoded schema
    public function isValid(): bool;        // whatever the cast supports
}

When — not if — you replace the package with a ~50-line cast around opis/json-schema, only the adapter changes. This is the single highest-leverage day-to-day habit with a package this young.

Pattern: use it on the write path, read defensively

The cast hydrates on read. If a row's stored JSON is malformed or no longer matches the validator's draft, a ->get() on a collection will throw and take the whole endpoint down with it. Two mitigations:

  • Backfill and lint before enabling the cast. Scan existing rows and either repair or null them. Never turn the cast on against a table you haven't audited.
  • Read schemas through a scoped query where you can tolerate failures — or catch and null in a custom subclass of the cast, rather than relying on the package to be lenient.

Pattern: pair with Laravel validation on the request path

The package is not a request validator. Wire the two halves explicitly:

// Store the schema
$definition->update(['schema' => $request->validated('schema')]);

// Validate incoming payloads against it, separately
$validator = Validator::make(
    $request->input('payload'),
    [], // no rules; validate with the schema instead
);

Then use a JSON Schema validator directly (the same one the package uses, or opis/json-schema) against $definition->schema->toArray(). Keeping validation out of the cast means you control fail-open vs fail-closed per endpoint and can attach human-readable messages.

Pattern: cache compiled schemas

If the validator compiles on every hydrate, cache by schema hash:

$compiled = Cache::remember('schema:'.md5($definition->schema->toJson()), 3600, fn () =>
    $validator->compile($definition->schema->toArray())
);

This is the difference between "fine" and "unusable" once a model is read in a ->each() or a queue worker. Don't enable the cast on models that are bulk-read but never need the schema object.

Pattern: feature-flag the rollout

Because the failure mode is a 500 on a list endpoint, gate the cast's usage behind a config flag so you can revert by flipping it:

// config/schema.php
'casts' => ['form_definitions' => env('SCHEMA_CAST_FORMS', false)],

Apply the cast conditionally in a model accessor until the feature is proven.

Gotchas and Tips

The validator, not the package, is what you're adopting.

The bundled library determines draft support (draft-04 vs draft-07 vs 2020-12), $ref resolution (local vs remote vs disabled), and format assertion semantics. Pin the validator's major and test draft behavior explicitly — draft-07 format is annotation-only by default in most libraries, which is a common and confusing surprise.

Fail-closed on read is the sharp edge.

find(), get(), chunk(), cursor(), lazy(), first(), and paginate() all hydrate the cast. A malformed document in one row breaks the entire collection. Audit fixtures before enabling on production data. Prefer a subclass that catches validator exceptions and returns null over relying on undocumented behavior.

Serialization shape is a contract.

toArray(), toJson(), and jsonSerialize() will each behave slightly differently depending on whether the cast returns a value object, an array, or an Arrayable. Assert on the exact shape in a test before shipping a resource that consumes it. When you later swap the cast out, this test is your migration guardrail.

Remote $ref resolution can be a hidden network call.

If the validator resolves HTTP $refs by default, model hydration now depends on the network. Disable remote resolution explicitly (setRemoteRetrievers([]) or the equivalent), and prefer $ref to local schema definitions only.

Bulk operations bypass the cast.

upsert(), insert(), update() via query builder, raw DB::statement(), other services writing to the same table, and database fixtures all skip the cast entirely. The cast is a convenience layer on read, not an enforcement mechanism. Never rely on it for data integrity.

Catch the Laravel/PHP version pin early.

The package's age almost certainly means an illuminate/support constraint that is older than your composer.json requires. Expect to either fork with a loosened constraint or composer require folklore/eloquent-json-schema -W and patch it. Vendor the fork into App\Support\Casts if you keep it — that removes the upgrade-time surprise and makes the eventual replacement a rename.

Check whether the cast implements CastsAttributes or the older Castable convention.

Laravel 7+ requires CastsAttributes (or CastsInboundAttributes) for the $casts array to work as expected. If the package predates that, the $casts syntax may subtly misbehave — verify by writing a round-trip test, not by reading the README.

Extension points are minimal; subclass rather than patch in place.

The package exposes few hooks. Subclass the cast, override the get()/set() methods, and inject your own validator or error handling. Keep the subclass in your app namespace so a future package upgrade (unlikely) doesn't silently overwrite your behavior.

Debugging tips.

  • When a hydration throws, dump the raw value with DB::table(...)->where(...)->first() — bypassing Eloquent entirely — to see if the stored JSON is what the validator rejects.
  • When a schema "should" be valid but isn't, test it directly against the validator outside Eloquent. That isolates schema bugs from cast bugs.
  • When toArray() returns something unexpected, var_dump($model->getCasts()) to confirm the cast is actually registered for that attribute — a mistyped key in $casts fails silently.

Document the behavior before rollout.

Because the upstream is dormant, your team's runbook is the support model. Write down, for the one model you enable: what the cast does on invalid input, how to diagnose a hydration failure, the schema draft the validator runs, and how to roll back (flip the flag, drop the $casts entry). That document is the difference between a five-minute incident and an afternoon under pressure.

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.
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
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels