Appearance
Python Integration
Use FormDSL forms with Django, Flask, FastAPI, or any Python backend. 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 <-- Python 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 Python using the
formdsl-validatePyPI package
Step 1: Export the manifest
From your frontend project:
bash
# Single form
npx formdsl-react manifest ./src/forms/ContactForm.tsx -o manifests/contact.json
# All forms in a directory
npx formdsl-react manifest ./src/forms/ -o 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.
Step 2: Install the Python validator
bash
pip install formdsl-validateRequirements: Python 3.10+, no external dependencies.
Step 3: Validate submissions
Django
python
# views.py
import json
from django.http import JsonResponse
from django.conf import settings
from formdsl_validate import validate_from_file
MANIFEST_PATH = settings.BASE_DIR / 'manifests' / 'contact.json'
def contact_submit(request):
if request.method != 'POST':
return JsonResponse({'error': 'Method not allowed'}, status=405)
data = json.loads(request.body)
result = validate_from_file(str(MANIFEST_PATH), data)
if not result.valid:
return JsonResponse({'errors': result.to_dict()}, status=422)
# Validation passed — process the submission
Contact.objects.create(**data)
return JsonResponse({'status': 'ok'})result.to_dict() returns errors grouped by field ID:
python
{
"name": ["This field is required"],
"email": ["Please enter a valid email address"]
}Django REST Framework
python
# views.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
from formdsl_validate import validate_from_file
@api_view(['POST'])
def contact_submit(request):
result = validate_from_file('manifests/contact.json', request.data)
if not result.valid:
return Response({'errors': result.to_dict()}, status=422)
serializer = ContactSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({'status': 'ok'}, status=201)Flask
python
# app.py
from flask import Flask, request, jsonify
from formdsl_validate import validate_from_file
app = Flask(__name__)
@app.post('/api/contact')
def contact_submit():
data = request.get_json()
result = validate_from_file('manifests/contact.json', data)
if not result.valid:
return jsonify(errors=result.to_dict()), 422
# Process the submission
db.session.add(Contact(**data))
db.session.commit()
return jsonify(status='ok')FastAPI
python
# main.py
from fastapi import FastAPI, HTTPException
from formdsl_validate import validate_from_file
app = FastAPI()
@app.post('/api/contact')
async def contact_submit(data: dict):
result = validate_from_file('manifests/contact.json', data)
if not result.valid:
raise HTTPException(status_code=422, detail=result.to_dict())
# Process the submission
await save_contact(data)
return {'status': 'ok'}Step 4: Wire up the frontend
Your React form submits to your Python 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' },
body: JSON.stringify(answers),
})
if (!res.ok) {
const { errors } = await res.json()
// Handle server-side validation errors
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="message" type="textarea" required label="Message" rows={4} />
</Section>
</Form>
)
}The client validates on submit (instant feedback), then your Python 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
validate_submission(manifest: dict, answers: dict) -> ValidationResult
Validate answers against a parsed manifest dictionary.
validate_from_file(manifest_path: str, answers: dict) -> ValidationResult
Load a manifest from a JSON file and validate.
ValidationResult
result.valid: bool— whether validation passedresult.errors: list[ValidationError]— list of errorsresult.to_dict() -> dict[str, list[str]]— errors grouped by field ID
ValidationError
error.field_id: str— the field that failederror.message: str— human-readable error message
Tips
Keep manifests in sync. Re-export after changing your form JSX. Add the manifest export to your CI/CD:
bash
npx formdsl-react manifest ./frontend/src/forms/ -o backend/manifests/Store manifests alongside your views. Wherever your routes/views live, keep manifests nearby for easy loading.
What's next
- Getting Started — Frontend-only setup
- Connected Mode — Full FormDSL server with auto-save and dashboard
- Laravel Integration — Same pattern for PHP/Laravel
- DSL Reference — Complete component and props reference