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

Liform Bundle Laravel Package

limenius/liform-bundle

LiformBundle generates JSON Schema from your Symfony forms, letting JavaScript libraries like liform-react render them client-side. It bridges server-side form definitions and modern frontend UIs, keeping validation rules and field structure in one place.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package — the first decision is whether you're actually on Symfony. If you're on Laravel, the bundle's DI wiring, config/packages/*.yaml tree, and symfony/form input contract are all dead weight; what you want is the limenius/liform core library, which is plain PHP. Read the core first (it walks a form-ish object graph and emits JSON Schema), and treat the bundle as a reference implementation of how to wire it.

If you're on Symfony (the intended path):

  1. composer require limenius/liform-bundle
  2. Register the bundle in config/bundles.php (Symfony Flex usually does this).
  3. Confirm the liform service is available — check DependencyInjection/LiformExtension.php for the config tree and defaults.
  4. Build one small FormType — text, email, number, one NotBlank and one Length constraint. Don't start with your most complex form.
  5. Expose it through a controller that calls the generator and returns the schema as JSON.
#[Route('/schema/contact')]
public function contactSchema(Liform $liform): JsonResponse
{
    $form = $this->createForm(ContactType::class, null, ['csrf_protection' => false]);
    return $this->json($liform->transform($form->createView()));
}
  1. Hit the route, save the JSON to a file, and render it in react-jsonschema-form or liform-react to confirm the loop end-to-end before touching another form.

First real use case: the flat contact/signup form. It exercises the constraint mapping that actually matters (required, length, pattern, enum) and gives you the schema shape your frontend team will consume. Nested collections and conditional fields are a second form, not the first — that's where generators leak.

If you're on Laravel: don't install the bundle. Spike limenius/liform + symfony/form in a throwaway branch only to inspect the output quality, then decide between writing a FormRequest → JSON Schema exporter yourself (2–4 weeks for common rules) or using existing schema tooling. The bundle is not a shortcut here.

Implementation Patterns

Dedicated schema controller/route per resource. Don't try to bolt schema output onto an existing action. One route, GET /schema/{form}, returns JSON. Keeps the concern isolated, cacheable, and easy to gate behind auth in non-dev environments.

Cache aggressively — schemas are deterministic. Given a FormType class and its constraints, output does not change between requests. Key a cache entry on the form class name + a hash of the type file (or just the class name if you're disciplined about deploys). This turns schema generation out of the request hot path entirely.

Build-time artifacts over runtime generation. A small console command or build step that dumps var/schema/*.json for every form type is the workflow that scales. Schemas then show up as diffs in review, they're versioned with the code, and the frontend can consume them from a static URL or bundled artifact. Runtime generation is fine for a spike, awkward in production.

Wire it behind your own adapter interface. Even if you're on Symfony, wrap the call in a one-method interface:

interface SchemaGenerator { public function for(string $formClass): array; }

If Liform stalls, is forked, or you migrate off Symfony, the change is one class. This is the single highest-value pattern in the whole assessment.

Custom transformers for UI hints. Liform supports transformers that mutate the emitted schema. Use them for things the form type doesn't express — ui:widget, ui:placeholder, ui:order, help text. Keep these in dedicated transformer classes so the base schema stays a faithful reflection of validation.

Integration tip — one form, two consumers. The realistic first integration is: same form type renders an HTML form and emits JSON Schema. Assert the two stay aligned with a snapshot test (save the emitted JSON, diff it in CI). That's what catches drift when someone adds a constraint but the frontend team never hears about it.

Naming/block-prefix config. The bundle exposes field-naming options — check LiformExtension for property_namer or block-prefix settings. If you're nesting forms or using CollectionType, the property names in the schema will reflect Symfony's block prefixes unless you configure this. Decide the convention once and pin it.

Gotchas and Tips

JSON Schema draft drift is the number-one silent bug. Liform historically defaults to draft-04. Draft-06/07/2020-12 differ on exclusiveMinimum, $defs vs definitions, and nullable vs type unions. Pin the draft explicitly as config, and validate the output against your target renderer's supported draft — not just "it looks like JSON Schema."

nullable is the mapping that trips everyone. A nullable string is absence from required plus ["string","null"], not type: "string" with an empty value. Test this specifically; most naive consumers and naïve generators get it wrong.

Custom form types and exotic constraints do not map. Your MoneyType, CountrySelectType, or required_if-style conditional logic will either silently disappear or throw. Liform's mapping is solid for the mainstream (NotBlank, Length, Regex, Email, Choice, Range) and thin at the edges. Audit your form types before promising the frontend anything — count how many use custom types.

Nested collections need verification. CollectionType with allow_add/prototype maps to items in the schema, but the recursive walk has edge cases (empty prototype, per-item constraints, nested collection in a collection). Build two representative nested forms and inspect the JSON before declaring victory.

Debugging: dump the raw schema, always. When a field is missing or wrong, don't guess — json_encode($liform->transform($form->createView()), JSON_PRETTY_PRINT) and diff against the form type definition. The codebase is the spec; the docs are thin. If a constraint isn't appearing, trace back to whether the constraint has a mapping in the guesser before assuming config is wrong.

Version-pin and watch Symfony majors. Symfony Form's internals (block prefixes, FormTypeInterface signatures, constraint metadata access) shift across 5.4 → 6.x → 7.x. Pin the bundle version, and expect to update it whenever you bump Symfony — the maintainer tracks majors on their own schedule, not yours.

Maintenance is a real cost, not a hypothetical. 141 stars, single dominant maintainer, zero listed dependents. Adopt as a replaceable utility behind your adapter (above), pilot on one non-critical form, and budget for the realistic possibility of forking if it stalls. The MIT license makes forking cheap; the lack of downstream users makes upstream bug fixes slower than you'd expect.

Extension point worth knowing: the Resolver / Guesser interfaces in the core library are the seams where you add mappings for custom constraints. If you find yourself patching behavior, that's where, not in the bundle's listeners.

Don't run this alongside OpenAPI tooling as a second source of contract truth. JSON Schema from forms and OpenAPI from resources/docs overlap badly. Pick one as the contract surface or keep them in clearly separate lanes.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager