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

Laravel Data Schemas Laravel Package

rushing/laravel-data-schemas

Define structured data schemas in Laravel with a fluent API, helping you validate, cast, and transform data consistently across requests, models, and APIs. Lightweight, expressive, and easy to integrate.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisite check first. This package has nothing to project unless your boundary shapes are already declared as spatie/laravel-data classes. If requests, responses, and event payloads are still arrays or ad-hoc DTOs, stop here — the real migration cost is to Data classes, not to this package. Confirm that pattern exists before installing anything.

Install and orient.

composer require rushing/laravel-data-schemas

Before writing code, read three things in this order:

  1. composer.json — which spatie/laravel-data and PHP/Laravel versions are pinned. That pin is your compatibility contract; you will track it forever.
  2. src/ — find the entry point (an artisan command, a facade, or a generator class). At 1 star / 0 dependents, assume the docs are thinner than the source. The source is the documentation.
  3. tests/ — if there are fixture-based golden-file tests, they double as your semantics reference for enums, nullables, nested collections, and unions.

First use case: an AI tool schema. This is the highest-leverage starting point because the alternative is hand-maintaining a JSON Schema that silently drifts from your validation rules. Take one existing Data class — ideally one with an enum, a nullable field, and a nested object — and generate from it:

// app/Data/ToolInputs/SearchQueryData.php
class SearchQueryData extends Data
{
    public function __construct(
        #[Required, Max(200)]
        public string $query,
        #[Optional, In(['title', 'body'])]
        public ?string $field,
    ) {}
}

Generate the schema for that one class, open the output, and compare it line-by-line against the hand-written schema you'd have written. If it is exact, you've validated the core promise on the construct that matters most. That single comparison is the entire decision gate — see Gotchas and Tips for what to look for in the diff.

Implementation Patterns

Data class → generated artifact → committed file. Treat the schema as a build artifact you check into version control, not something generated on every request. The loop is:

  1. Change the Data class.
  2. Regenerate the schema (artisan command or programmatic generator — verify which exists in a spike).
  3. Diff the artifact. If it changed unexpectedly, you've caught a shape change before it reached a consumer.

This is the pattern that makes the package valuable: the artifact is diffable, reviewable, and versioned alongside the class it projects.

CI drift check. The most important integration point. Add a CI step that regenerates schemas and fails if git diff is non-empty:

php artisan data:schemas:generate   # confirm the actual command name in src/
git diff --exit-code schemas/

Without this, stale schemas are silent. With it, a Data class change that isn't reflected in the artifact breaks the build — which is exactly what you want, because the downstream consumer (frontend validator, AI SDK, docs) is now out of sync.

One generation path per consumer. Different consumers want different shapes from the same source:

  • AI tool definitions — emit one schema per Data class used as a tool input/output.
  • Frontend validation — feed generated JSON Schema to a Zod/Valibot generator; derive types from the emitted schema, not from a wiki.
  • API docs — publish schema files as reference artifacts. Note this is schema-level, not full OpenAPI — it won't give you paths, auth, or error envelopes.

Keep the generation pipeline per-consumer, but the source Data class singular.

Nested and reused types. If your generator emits $defs/definitions with $ref for reused Data classes, structure your code so that shared value objects (Money, Address, etc.) are their own Data classes. That way the same $ref appears everywhere and consumers cache one definition. If your generator inlines everything, duplicated schemas are a maintenance cost you'll need to budget for — verify which behavior you get.

Enforce the discipline yourself. The package's doctrine is "a declared Data class is the input, always." It does not enforce that every boundary shape is a Data class. Add an architecture test (e.g., a Pest test asserting no controller returns array typed methods for API routes) if you want the doctrine to hold. The package gives you the projection; the coverage guarantee is your CI's job.

Golden-file tests in your app. Once you adopt, write a small fixture set in your test suite covering one Data class per construct you use: flat, nested, enum, nullable, union, collection, recursive. Compare output against committed fixtures. This is what catches a Spatie Data upgrade silently changing projection semantics — the package's own test suite is not a substitute, since it tests constructs the maintainer chose.

Gotchas and Tips

Maturity is the dominant risk, not capability. 1 star, 0 dependents, single maintainer. Treat this as code you own from the moment you install it — vendor it or fork it under your org, and check whether the generation path is small enough to patch without regret. Do not put it on a critical path where a generation failure blocks a release. The license (MIT) permits forking; the support posture does not permit expecting upstream fixes.

Statical rules cannot be projected — verify what happens to them. exists, unique, Rule::in(...) sourced from a DB query, and closure rules depend on runtime state that JSON Schema cannot express. The package's central promise is faithful projection. What does it do with an unrepresentable rule — drop it silently, throw, or annotate? The answer determines whether the schema under-specifies validation in a way a frontend validator would not catch. Run this test explicitly in the spike, and if rules are dropped silently, maintain an explicit "not representable in JSON Schema" allowlist in your own docs.

It never infers. Anything undeclared yields nothing. Arrays, docblocks, and runtime samples are not inputs. If a boundary shape isn't a Data class, the generator has no output for it — there is no partial result and no warning. This is the design, and it's correct, but it means your adoption strategy must include migrating boundary shapes to Data classes, and CI enforcement, or coverage degrades to "whatever happened to be declared."

Semantic gaps to test explicitly. The PHP-to-JSON-Schema mapping is where drift bugs live. Build one fixture per construct and get a human who owns the downstream consumer to sign off:

  • ?string — is it type: ["string","null"] or type: string + omission?
  • Optional — does it disappear from required or map to some other signal?
  • Enums — enum: [...] values, or $ref to a definition?
  • Union types (string|int) — anyOf, or a flattened type list?
  • DataCollection / Collection<int, Foo> — type: array with items: $ref?
  • Recursive types — does the generator terminate, or recurse?

JSON Schema draft target is a compatibility decision, not a package detail. If the package emits draft-07 but your downstream tooling (an OpenAPI 3.0 stack, for instance) expects 2020-12 — or vice versa — you own the translation step. Check the emitted $schema and treat it as a pinned value in your compatibility matrix.

Deterministic output matters for CI. Stable key ordering in the emitted JSON is what makes the drift check useful. If property order wobbles between runs, you'll get spurious diffs and people will start ignoring the check. Verify by generating the same class twice and diffing.

Pin Spatie Data, upgrade with diffs. The package reflects Spatie Data internals — attributes, type resolution, Optional. Spatie Data ships breaking-adjacent changes frequently. Pin the version, and on every upgrade, regenerate and review the schema diff before merging. This is a five-minute step that prevents a class of production bugs.

Vendored AGENTS.md. If you vendor this package (or a family sibling) into vendor/, check the vendored repo's own AGENTS.md for conventions it ships with before editing through into it. You're otherwise taking on a maintenance surface you didn't price.

Not the right tool for adjacent jobs. It generates schema artifacts — it does not do runtime validation (the Laravel validator already does), and it does not produce OpenAPI documents (paths, auth, error envelopes are out of scope). Don't grow scope onto it; keep the generation path narrow so a fork stays cheap.

Silent drift is the failure mode to design against. Every mitigation above — CI regeneration, golden-file tests, pinned Spatie Data version, committed artifacts — exists to convert silent drift into a loud failure. If you only do one thing from this assessment, do the CI drift check.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle