Enterprise-grade, database-driven form builder with multi-stage approval workflows, external data integration, and cross-instance sync

Django Forms Workflows bridges the gap between simple form libraries (like Crispy Forms) and expensive SaaS solutions (like JotForm, Formstack). It provides:
- 📝 Database-Driven Forms — Define forms in the database, not code. 25+ field types including calculated/formula fields and spreadsheet uploads.
- 🔄 Multi-Stage Approval Workflows — Sequential, parallel, or hybrid approval flows with configurable stages and conditional trigger logic.
- ↩️ Send Back for Correction — Approvers can return a submission to any prior stage without terminating the workflow.
- 🎯 Dynamic Assignees — Resolve individual approvers at submit time from form field values (email, username, full name, or LDAP lookup).
- 🔀 Sub-Workflows — Spawn child workflows from a parent submission (e.g. one form creates N payment approvals).
- 🔌 External Data Integration — Prefill fields from LDAP, databases, REST APIs, or the Django user model.
- ⚡ Post-Submission Actions — Trigger emails, database writes, LDAP updates, or custom Python handlers on submit/approve/reject.
- 🔄 Cross-Instance Sync — Push/pull form definitions between environments directly from the Django Admin.
- 🔒 Enterprise Security — LDAP/AD & SSO authentication, RBAC with four permission tiers, complete audit trails.
- 🌐 REST API — Opt-in Bearer-token API: list forms, fetch field schema, submit (or save draft), poll status. OpenAPI 3.0 schema + Swagger UI included.
- 🖼️ Embeddable Forms — Embed forms on any website via a <script> tag or <iframe>. Auto-resize, theme/accent color support, postMessage callbacks. WordPress plugin included.
- 💳 Pluggable Payments — Collect payments during form submission with a pluggable provider system. Stripe ships built-in; add custom providers via register_provider().
- 📋 Shared Option Lists — Define reusable choice lists once, reference from any form field. Update the list and every field reflects the change.
- 🔗 Dependent Workflows — Workflows that start only after all other workflows on the form complete, enabling convergence gates after parallel tracks.
- 📁 Managed File Uploads — File uploads with approval, rejection, and version tracking per submission.
- 🧮 Formula Fields — Calculated fields that compute values live from other field values using a template formula.
- 🏠 Self-Hosted — No SaaS fees, full data control.
Business users create and modify forms through Django Admin:
- 25+ field types: text, email, phone, select, radio, checkbox, checkboxes, multiselect, date, time, datetime, decimal, currency, number, URL, file, multi-file, textarea, hidden, section headers, calculated/formula, spreadsheet upload (CSV/Excel), country picker, US state picker, signature (draw or type)
- Field ordering with explicit order integer
- Column-width layout per field: full, half, one-third, one-quarter
- Validation rules (required, regex, min/max length, min/max value)
- Conditional field visibility (show_if_field / show_if_value)
- Custom help text, placeholders, and CSS classes
- Read-only and pre-filled fields
- Draft saving with auto-save support
🔄 Multi-Stage Approval Workflows
Flexible approval engine built on WorkflowStage records:
- Each stage has its own approval groups and logic (any / all / sequence)
- Stages execute in order; next stage unlocks when the current one completes
- Multiple WorkflowDefinition records can run as parallel approval tracks on the same form
- Conditional stages — each stage can carry trigger_conditions JSON; stages whose conditions don't match the submission data are automatically skipped
- Workflow-level trigger conditions — entire parallel approval tracks are skipped when their workflow trigger_conditions don't match (e.g. only trigger a Finance track when amount > 10000)
- Dynamic individual assignees — set assignee_form_field + assignee_lookup_type (email / username / full_name / ldap) on a stage to resolve the approver from a form field value at runtime; falls back to group tasks when the value cannot be resolved
- Send Back for Correction — approvers can return a submission to any prior stage that has allow_send_back enabled without terminating the workflow; full audit entry recorded
- Stage-specific form fields (e.g. approver notes, signature date) appear only during that stage
- Configurable approval deadline (approval_deadline_days) sets due_date on created tasks
- Reminder emails (send_reminder_after_days) and optional auto-approval after expiry (auto_approve_after_days)
- Immediate or batched notifications (immediate / daily / weekly / monthly / form_field_date)
- Optional per-stage reassignment, editable submission data during approval, custom approve button labels, hidden approval history, and bulk export / bulk PDF export controls
- Rejection handling with per-stage or global rejection semantics
- Complete audit trail on every approval, rejection, send-back, and status change
- First-class outbound webhooks per workflow with signed async delivery, retry/backoff, and delivery logs
See the Workflow Guide, Visual Workflow Builder, Dynamic Assignees, Send Back for Correction, Notifications, and Workflow Webhooks.
Spawn child workflow instances from a parent submission:
- SubWorkflowDefinition links a parent workflow to a child form definition
- count_field controls how many sub-workflows to create (driven by a form field value)
- section_label and label_template control how child instances are presented in the UI
- data_prefix slices the parent's form data to populate each child
- Triggered on_submission or on_approval
- detached mode lets child instances complete independently of the parent
- reject_parent=True lets a rejected child immediately reject the parent and cancel sibling child instances
- Parent submission status reflects aggregate child completion when not detached
- Sub-workflows support the same send-back mechanism via handle_sub_workflow_send_back
See Sub-Workflows Guide for a full walkthrough.
Embed DFW forms on any external website:
- Single <script> tag creates a responsive iframe with auto-resize via postMessage
- Configurable theme (light/dark), accent color, min height
- Callbacks: data-on-submit, data-on-load for parent page integration
- WordPress plugin included ([dfw_form] shortcode + Gutenberg block)
- Admin embed code panel with copy-to-clipboard snippets
- Inline success state — no redirects, iframe stays self-contained
See Embedding Guide.
Collect payments as part of the form submission flow:
- Pluggable provider architecture: PaymentProvider ABC, provider registry, PaymentResult dataclass
- Two flow types: INLINE (payment form on your site, e.g. Stripe Elements) and REDIRECT (external payment page)
- Built-in Stripe provider using PaymentIntents with automatic payment methods
- PaymentRecord model tracks full payment lifecycle (pending → completed/failed/refunded)
- Configurable per form: provider, amount (fixed or from field), currency, description template
- Custom providers self-register via register_provider() in AppConfig.ready()
See Payment System Guide.
Define reusable choice lists shared across forms:
- SharedOptionList model with name, slug, items (JSON array of strings or value/label objects)
- shared_option_list FK on FormField overrides inline choices for select, radio, multiselect, checkboxes
- Choice resolution priority: database prefill → shared option list → inline choices
- Update a list once, every referencing field updates immediately
- Form builder UI with shared list dropdown on choice-based fields
See Shared Option Lists Guide.
Workflows that wait for other workflows to complete before starting:
- start_trigger = "on_all_complete" on WorkflowDefinition
- Enables convergence patterns: parallel tracks must all finish before a final review step
- Peer-level relationship (not parent/child like sub-workflows)
- Complements sub-workflows — both can coexist on the same form
See the Dependent Workflows section of the Workflows Guide.
🧮 Calculated / Formula Fields
Auto-compute field values from other field inputs:
- field_type = "calculated" with a formula template string (e.g. {first_name} {last_name})
- Live client-side evaluation updates the read-only field as the user types
- Authoritative server-side re-evaluation on submit prevents tampering
- Available in both regular form submission and approval step forms
See Calculated Fields Guide.
📊 Spreadsheet Upload Fields
Accept CSV or Excel files as structured form input:
- field_type = "spreadsheet" with accept .csv, .xls, .xlsx
- Parsed and stored as {"headers": [...], "rows": [{...}, ...]} in form_data
- Available during initial submission and approval step forms
🔌 Configurable Prefill Sources
Populate form fields automatically from reusable PrefillSource records:
- User model — user.email, user.first_name, user.username, etc.
- LDAP / Active Directory — any LDAP attribute (department, title, manager, custom)
- External databases — schema/table/column lookup with template support for multi-column composition
- Custom database queries — reference a named query via database_query_key
- System values — current_date, current_time
⚡ Post-Submission Actions
Automatically run side-effects after a submission event:
| Trigger |
Description |
| on_submit |
Runs immediately on form submission |
| on_approve |
Runs when the submission is approved |
| on_reject |
Runs when the submission is rejected |
| on_complete |
Runs when the entire workflow completes |
Action types: email, database, ldap, api, custom
Features:
- Conditional execution with 10 operators (equals, not_equals, greater_than, less_than, contains, not_contains, is_empty, is_not_empty, is_true, is_false, plus date comparisons)
- Automatic retries with configurable max_retries
- Execution ordering for dependent actions
- Idempotent locking (is_locked) to prevent double-execution
- Full execution logging via ActionExecutionLog
- Pluggable handler architecture — register custom handlers via FORMS_WORKFLOWS_CALLBACKS setting or register_handler() API; use short names in the admin instead of full Python paths
🔄 Cross-Instance Form Sync
Move form definitions between environments from the Django Admin:
- Pull from Remote — connect to a configured remote instance and import selected forms
- Push to Remote — select forms and push to any destination
- Import / Export JSON — portable .json snapshots
- Conflict modes — update, skip, or new_slug
- FORMS_SYNC_REMOTES setting — pre-configure named instances (URL + token)
- HTTP endpoints protected by Bearer token for CI/scripted use
- FileUploadConfig per form definition (allowed extensions, max size)
- ManagedFile records with approval/rejection/supersede lifecycle
- Version tracking with is_current flag
🔒 Enterprise-Ready Security
- LDAP/Active Directory authentication with auto-sync of profile attributes
- SSO integration (SAML, OAuth) with attribute mapping to UserProfile
- Role-based access — four permission tiers on FormDefinition:
- submit_groups — can see and submit the form
- view_groups — prerequisite gate for form access
- reviewer_groups — read-only view of all submissions and approval history
- admin_groups — full administrative view of all submissions
- Group-based approval routing via WorkflowStage.approval_groups
- Complete audit logging (AuditLog — who, what, when, IP address)
- Public / anonymous forms — mark any form as requires_login=False and anonymous users can submit it; IP-based rate limiting prevents abuse
- UserProfile auto-created on first login with LDAP/SSO sync
pip install django-forms-workflows
- Add to INSTALLED_APPS:
INSTALLED_APPS = [
# ...
'nested_admin',
'crispy_forms',
'crispy_bootstrap5',
'django_forms_workflows',
]
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"
- Include URLs and run migrations:
# urls.py
urlpatterns = [
re_path(r'^_nested_admin/', include('nested_admin.urls')),
path('forms/', include('django_forms_workflows.urls')),
]
python manage.py migrate django_forms_workflows
- Create your first form in Django Admin!
FORMS_WORKFLOWS = {
# Branding — replaces "Django Forms Workflows" across all templates
"SITE_NAME": "Acme Corp Workflows",
# LDAP attribute sync on every login
"LDAP_SYNC": {
"enabled": True,
"attributes": {
"department": "department",
"title": "title",
"employee_id": "extensionAttribute1",
},
},
}
# Cross-instance form sync
FORMS_SYNC_API_TOKEN = "shared-secret"
FORMS_SYNC_REMOTES = {
"production": {
"url": "https://prod.example.com/forms-sync/",
"token": "prod-token",
},
}
# Custom callback handlers — register by short name instead of full Python paths
# These names can be used in PostSubmissionAction.custom_handler_path
FORMS_WORKFLOWS_CALLBACKS = {
"id_photo_copy": "myapp.handlers.IDPhotoHandler",
"sync_to_erp": "myapp.handlers.ERPSyncHandler",
}
# Context processor — injects site_name into every template
TEMPLATES = [
{
"OPTIONS": {
"context_processors": [
# ... standard processors ...
"django_forms_workflows.context_processors.forms_workflows",
]
}
}
]
graph TB
subgraph UI["User Interface"]
FB["Form Builder<br/>(Admin)"]
FV["Form Viewer<br/>(End User)"]
AU["Approval UI<br/>(Approvers)"]
end
subgraph Core["Django Forms Workflows"]
FD["FormDefinition<br/>+ FormField"]
WF["WorkflowDefinition<br/>+ WorkflowStage"]
PS["PrefillSource"]
PA["PostSubmissionAction<br/>+ Executor"]
SYNC["Sync API<br/>(Push/Pull)"]
end
subgraph External["External Systems"]
AD["LDAP / AD"]
DB["External<br/>Databases"]
API["REST APIs"]
SSO["SSO<br/>(SAML/OAuth)"]
end
FB --> FD
FV --> FD
AU --> WF
FD --> PS
FD --> PA
FD --> SYNC
PS --> AD
PS --> DB
PA --> DB
PA --> AD
PA --> API
SSO --> Core
Loading
- HR — Onboarding, time-off requests, expense reports, status changes
- IT — Access requests, equipment requests, change management
- Finance — Purchase orders, invoice approvals, budget requests
- Education — Student applications, course registrations, facility booking
- Healthcare — Patient intake, referrals, insurance claims
- Government — Permit applications, FOIA requests, citizen services
| Feature |
Django Forms Workflows |
Crispy Forms |
FormStack |
Django-Formtools |
| Database-driven forms |
✅ |
❌ |
✅ |
❌ |
| No-code form creation |
✅ |
❌ |
✅ |
❌ |
| Self-hosted |
✅ |
✅ |
❌ |
✅ |
| Multi-stage approval workflows |
✅ |
❌ |
⚠️ |
❌ |
| Sub-workflows |
✅ |
❌ |
❌ |
❌ |
| Post-submission actions |
✅ |
❌ |
⚠️ |
❌ |
| External data prefill |
✅ |
❌ |
⚠️ |
❌ |
| REST API (Bearer token) |
✅ |
❌ |
✅ |
❌ |
| Bulk export (Excel / CSV) |
✅ |
❌ |
✅ |
❌ |
| Cross-instance sync |
✅ |
❌ |
❌ |
❌ |
| LDAP/AD + SSO integration |
✅ |
❌ |
❌ |
❌ |
| Managed file uploads |
✅ |
❌ |
✅ |
❌ |
| Audit trail |
✅ |
❌ |
✅ |
❌ |
| Open source |
✅ |
✅ |
❌ |
✅ |
- Python 3.10+
- Django 5.2+
- PostgreSQL, MySQL, or SQLite (PostgreSQL recommended for production)
- Optional: Celery 5.0+ with Redis/Valkey for background task processing
- Optional: openpyxl for Excel spreadsheet field support (pip install django-forms-workflows[excel])
- Optional: django-auth-ldap for LDAP/AD integration (pip install django-forms-workflows[ldap])
- Optional: WeasyPrint for PDF export (pip install django-forms-workflows[pdf])
- Optional: Node.js ^20.19.0 or >=22.12.0 / npm, to run the JS test suite (npm test) covering the visual Form/Workflow Builder's client-side code (an .nvmrc is committed at the repo root)
cd django-forms-workflows
pip install pytest pytest-django
python -m pytest tests/ -v
The test suite covers models, forms, workflow engine (including dynamic assignees, conditional stages, multi-workflow parallel tracks, sub-workflows), sync API, post-submission action executor, views, signals, conditions, and utilities — 298 tests.
The visual Form Builder's client-side JS (django_forms_workflows/static/django_forms_workflows/js/form-builder.js) has its own suite, run with Vitest:
npm run test:watch re-runs on file changes. Coverage is still minimal (tooling was just added) — see CONTRIBUTING.md if you're adding to it.
We welcome contributions! Please see CONTRIBUTING.md for details.
GNU Lesser General Public License v3.0 (LGPLv3) — see LICENSE for details.
See docs/ROADMAP.md for the full prioritized roadmap with rationale and implementation notes.
✅ Delivered (through v0.48)
- Database-driven form definitions with 25+ field types
- Dynamic form rendering with Crispy Forms + Bootstrap 5
- Multi-stage approval workflows (any/all/sequence logic per stage)
- Conditional workflow & stage trigger logic
- Dynamic individual assignee resolution (email / username / full name / LDAP)
- Send Back for Correction (return to any prior stage without terminating)
- Sub-workflow support (spawn N child instances from a parent)
- Calculated / formula fields with live client-side evaluation
- Spreadsheet upload fields (CSV, XLS, XLSX)
- LDAP/AD integration with profile sync
- SSO attribute mapping (SAML/OAuth)
- Configurable prefill sources (user, LDAP, database, API, system values)
- Post-submission actions with conditional execution, ordering & retries
- Cross-instance form sync (push/pull/JSON import-export)
- Managed file uploads with approval lifecycle and S3/Spaces presigned URLs
- Conditional field visibility (client-side JS, no page reload)
- Form templates and cloning
- Nested category hierarchy with group-based access control
- WeasyPrint PDF export with multi-column layout
- Column-picker approval inbox with DataTables
- Complete audit logging (who, what, when, IP)
- Configurable site branding via FORMS_WORKFLOWS['SITE_NAME']
- Comprehensive test suite (298 tests)
- REST API — Bearer-token authenticated endpoints: list forms, fetch field schema, submit (JSON or multipart, including ?draft=1), poll submission status; OpenAPI 3.0 schema + Swagger UI
- Bulk export — Excel and CSV export of submission data from the approval inbox (allow_bulk_export / allow_bulk_pdf_export per workflow)
- Four-tier RBAC — submit_groups, view_groups, reviewer_groups (read-only submission history), admin_groups (full submission view) with consistent enforcement across all list and detail views
- Auto-save with configurable interval; Save Draft bypasses required-field validation without storing browser internals (CSRF token, button names)
- Signature field type (drawn or typed) — v0.45.0
- Form versioning — ChangeHistory tracking, sync API snapshots, admin diff viewer action — v0.45.0
- Advanced reporting dashboard (submission analytics, approval times, bottleneck stages) — v0.46.0
- Settings-based callback handler registry (FORMS_WORKFLOWS_CALLBACKS) — register custom handlers by name instead of hardcoding Python paths in the database — v0.48.0
- First-class workflow webhooks with HMAC signing, async retry/backoff, admin configuration, delivery logs, cloning, and sync support
🚧 Near-term (next 1–3 releases)
Built with ❤️ by the Django community.
Special thanks to: