Appearance
Laravel Integration
Use FormDSL forms in your Laravel app with server-side validation in PHP. Your frontend uses the JSX DSL, your backend validates against the same rules using the exported manifest.
How it works
React/SolidJS form (JSX)
|
v
formdsl manifest <-- CLI extracts validation rules
|
v
contact.manifest.json <-- portable JSON contract
|
v
FormDSL\Validate <-- PHP package validates against it- Author your form in JSX using
@formdsl/react - Export the manifest (a JSON file describing all fields and validation rules)
- Validate submissions in PHP using the
formdsl/validateComposer package
This means your Laravel backend validates using the exact same rules as the client, without running Node.js or a sidecar service.
Step 1: Export the manifest
From your frontend project:
bash
# Single form
npx formdsl-react manifest ./src/forms/ContactForm.tsx -o resources/manifests/contact.json
# All forms in a directory
npx formdsl-react manifest ./src/forms/ -o resources/manifests/The manifest is a JSON file containing all field definitions, types, validation constraints, and conditions. Commit it to your repo or generate it in CI.
Example manifest (simplified):
json
{
"formId": "contact",
"version": "1.0",
"fields": [
{
"id": "name",
"type": "text",
"required": true,
"label": "Full Name",
"minLength": 2,
"maxLength": 100
},
{
"id": "email",
"type": "email",
"required": true,
"label": "Email"
},
{
"id": "role",
"type": "select",
"required": true,
"options": [
{ "value": "dev", "label": "Developer" },
{ "value": "design", "label": "Designer" }
]
},
{
"id": "github",
"type": "url",
"label": "GitHub Profile",
"showIf": { "role": "dev" }
}
]
}Step 2: Install the PHP validator
bash
composer require formdsl/validateRequirements: PHP 8.1+, no external dependencies.
Step 3: Validate in your controller
php
<?php
namespace App\Http\Controllers;
use FormDSL\Validate\Validator;
use Illuminate\Http\Request;
class ContactController extends Controller
{
public function store(Request $request)
{
$manifest = json_decode(
file_get_contents(resource_path('manifests/contact.json')),
true
);
$result = Validator::validate($manifest, $request->all());
if (!$result->valid) {
return back()->withErrors($result->toMessageBag())->withInput();
}
// Validation passed — process the submission
Contact::create($request->only(['name', 'email', 'role', 'github']));
return redirect()->route('contact.thanks');
}
}$result->toMessageBag() returns errors in Laravel's native format:
php
[
'name' => ['This field is required'],
'email' => ['Please enter a valid email address'],
]This works with Blade's @error directive, Inertia's shared errors, and any other Laravel validation error display.
Step 4: Wire up the frontend
Your React form submits to your Laravel endpoint:
tsx
import { Form, Section, Question } from '@formdsl/react'
import '@formdsl/react/styles'
export default function ContactForm() {
const handleSubmit = async (answers: Record<string, unknown>) => {
const res = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
body: JSON.stringify(answers),
})
if (!res.ok) {
const { errors } = await res.json()
// Handle validation errors from Laravel
return
}
window.location.href = '/contact/thanks'
}
return (
<Form id="contact" version="1.0" onSubmit={handleSubmit}>
<Section title="Contact">
<Question id="name" type="text" required label="Full Name" />
<Question id="email" type="email" required label="Email" />
<Question id="role" type="select" required label="Role" options={[
{ value: 'dev', label: 'Developer' },
{ value: 'design', label: 'Designer' },
]} />
<Question id="github" type="url" label="GitHub Profile"
showIf={{ role: 'dev' }} />
</Section>
</Form>
)
}The client validates on submit (instant feedback), then your Laravel backend validates again server-side (security).
What the validator checks
- Conditional visibility: Fields hidden by
showIfor disabled bydisabledIfare skipped - Required fields: Respects
requiredandrequiredIfconditions - Type-specific rules: Format, length, range, pattern, options lists, file types
- Repeat groups: Recursively validates children within repeat arrays
- Cross-field matching:
matchconstraints (e.g., confirm email)
API
Validator::validate(array $manifest, array $answers): ValidationResult
Validate answers against a parsed manifest array.
Validator::validateFromFile(string $path, array $answers): ValidationResult
Load manifest from a JSON file and validate.
ValidationResult
$result->valid—bool, whether validation passed$result->errors— array ofValidationErrorobjects$result->toMessageBag()—array<string, string[]>, Laravel-compatible error format
ValidationError
$error->fieldId— the field that failed$error->message— human-readable error message
Tips
Keep manifests in sync. Re-export after changing your form JSX. Add the manifest export to your CI/CD pipeline:
bash
# In your build script or CI
npx formdsl-react manifest ./src/forms/ -o resources/manifests/Store manifests in resources/manifests/. Laravel's resource_path() makes them easy to load.
API routes with Inertia. If you're using Inertia, validation errors are automatically shared with the frontend via $page.props.errors.
What's next
- Getting Started — Frontend-only setup
- Connected Mode — Full FormDSL server with auto-save and dashboard
- Python Integration — Same pattern for Django/Flask/FastAPI
- DSL Reference — Complete component and props reference