Skip to content

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
  1. Author your form in JSX using @formdsl/react
  2. Export the manifest (a JSON file describing all fields and validation rules)
  3. Validate submissions in Python using the formdsl-validate PyPI 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-validate

Requirements: 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 showIf or disabled by disabledIf are skipped
  • Required fields: Respects required and requiredIf conditions
  • Type-specific rules: Format, length, range, pattern, options lists, file types
  • Repeat groups: Recursively validates children within repeat arrays
  • Cross-field matching: match constraints (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 passed
  • result.errors: list[ValidationError] — list of errors
  • result.to_dict() -> dict[str, list[str]] — errors grouped by field ID

ValidationError

  • error.field_id: str — the field that failed
  • error.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