A BPMN editor integration with Frappe Framework, powered by bpmn-js and SpiffWorkflow extensions. The app provides a Vue.js-based BPMN process modeler accessible at /spiff, with support for multiple diagrams per process, a tabbed editing interface, a formatting toolbar, a custom shape library, and SpiffWorkflow properties panel integration.
- Frappe Bench setup
- Node.js 20.x
- Yarn package manager
# Get the app
bench get-app one_bpmn <repository-url>
# Install on your site
bench --site your-site.local install-app one_bpmn
# Run database migrations
bench --site your-site.local migrate# Navigate to the Vue.js frontend directory
cd apps/one_bpmn/spiff
# Install dependencies
yarn install
# Build for production
yarn build
# Copy assets to sites/assets
cd ../../../
bench build --app one_bpmn
# Clear cache
bench --site your-site.local clear-cachecd apps/one_bpmn/spiff
yarn dev --hostAccess at http://localhost:8080/spiff (dev server).
one_bpmn/
├── one_bpmn/ # Frappe app module
│ ├── api.py # Backend API endpoints (Process Models + Shape Library)
│ ├── hooks.py # Frappe hooks configuration
│ ├── one_bpmn/ # DocTypes module
│ │ └── doctype/
│ │ ├── bpmn_process_model/ # BPMN Process Model DocType
│ │ ├── bpmn_shape_library/ # Shape Library DocType
│ │ └── bpmn_custom_shape/ # Custom Shape DocType
│ ├── public/
│ │ └── spiff/ # Built Vue.js assets (generated)
│ │ ├── assets/ # JS, CSS, fonts
│ │ └── index.html
│ └── www/
│ └── spiff/ # Frappe www route
│ ├── index.html # HTML template (Jinja)
│ └── index.py # Context provider
│
└── spiff/ # Vue.js frontend source
├── package.json
├── vite.config.js
├── tailwind.config.cjs
├── postcss.config.cjs
├── index.html # Dev entry point
└── src/
├── main.js # App entry point
├── main.css # Global styles (Tailwind)
├── App.vue # Root component
├── dayjs.js # DayJS configuration
├── router/
│ └── index.js # Vue Router configuration
├── views/
│ ├── Home.vue # Process list (table layout)
│ └── Editor.vue # BPMN editor with tabbed interface
├── components/
│ ├── BpmnEditor.vue # bpmn-js modeler wrapper with SpiffWorkflow extensions
│ ├── EditorTabs.vue # Bottom tab bar for open diagrams
│ ├── EditorSidebar.vue # Left sidebar for diagram list
│ ├── FormattingToolbar.vue # Font, size, color, and alignment controls
│ └── ShapeLibraryPanel.vue # Custom shape library panel (drag-and-drop)
├── bpmn/
│ ├── CustomShapeRenderer.js # Renders custom SVG shapes on canvas
│ └── index.js # Custom shape module registration
├── renderers/
│ ├── CustomTextStyleRenderer.js # Renders custom text styles (font, color, size)
│ └── index.js # Text style module registration
├── rules/
│ ├── CustomRules.js # Custom modeling rules for shape connections
│ └── index.js # Rules module registration
├── moddle/
│ └── customTextStyleModdle.js # Moddle extension for text style XML attributes
├── i18n/
│ ├── customTranslate.js # Translation strings
│ └── index.js # Translation module registration
└── utils/
└── textStyleUtils.js # Text style calculation utilities
Located in one_bpmn/api.py:
| Method | Endpoint | Description |
|---|---|---|
| POST | one_bpmn.api.save_process_model |
Save or update a BPMN diagram |
| GET | one_bpmn.api.get_process_model |
Get a diagram by name |
| GET | one_bpmn.api.list_process_models |
List all diagrams |
| GET | one_bpmn.api.list_processes |
List Process records with per-process diagram counts |
| GET | one_bpmn.api.get_process_diagrams |
Get all diagrams for a specific process |
| POST | one_bpmn.api.update_diagram_order |
No-op (kept for frontend compatibility) |
| DELETE | one_bpmn.api.delete_diagram |
Delete a diagram |
| Method | Endpoint | Description |
|---|---|---|
| GET | one_bpmn.api.get_shape_libraries |
Get all shape libraries with nested shapes |
| POST | one_bpmn.api.create_shape_library |
Create a new shape library |
| DELETE | one_bpmn.api.delete_shape_library |
Delete a library and all its shapes |
| POST | one_bpmn.api.upload_shape |
Upload a custom SVG shape to a library |
| DELETE | one_bpmn.api.delete_shape |
Delete a custom shape |
All endpoints require authentication and use @frappe.whitelist() decorator.
Stores BPMN process definitions with SpiffWorkflow engine data. Named by title field.
| Field | Type | Description |
|---|---|---|
title |
Data (unique, required) | Diagram title (used as document name) |
process_name |
Link → Process | Parent process record |
process_id |
Data (unique, required) | BPMN process ID extracted from XML (e.g., Process_1) |
category |
Data | Optional category for grouping |
is_active |
Check (default: 1) | Whether the diagram is active |
description |
Small Text | Optional description |
bpmn_xml |
Code (XML) | BPMN XML content |
dmn_xml |
Code (XML) | DMN decision table XML |
serialized_spec |
JSON | SpiffWorkflow serialized process spec |
subprocess_specs |
JSON | SpiffWorkflow subprocess specifications |
version |
Int (default: 1) | Auto-incrementing version number |
amended_from |
Link → BPMN Process Model | Reference to previous version |
Controller logic (bpmn_process_model.py):
validate()— validatesprocess_idformat (alphanumeric, underscores, hyphens, dots) and auto-extracts it from BPMN XML if not setbefore_save()— auto-incrementsversionon each update
Permissions: System Manager (full), BPMN Admin (full), All (read-only).
Container for organizing custom shapes into libraries.
Stores individual custom SVG shapes linked to a parent library. Supports drag-and-drop onto the BPMN canvas.
| Route | View | Description |
|---|---|---|
/spiff |
Home.vue | List of Process records with diagram counts |
/spiff/process/:process |
Editor.vue | Editor for a process (shows all diagrams) |
/spiff/process/:process/diagram/:diagram |
Editor.vue | Editor with specific diagram active |
- Table listing Process records with per-process diagram counts
- Columns: Title, Process Owner, Business Analyst, Status, Last Modified, Created
- Status derived from the most recent diagram's
is_activeflag - Click a row to open the process editor
- Full-featured BPMN modeler powered by bpmn-js v17
- Tabbed interface at bottom for switching between diagrams
- Left sidebar for diagram list within the process
- Properties panel (toggleable) with BPMN and SpiffWorkflow properties
- Formatting toolbar with:
- Font family and size selection
- Bold, italic, underline text styling
- Text and fill color pickers
- Stroke color picker
- Text alignment (left, center, right)
- Toolbar with Undo/Redo/Delete buttons
- Keyboard shortcuts:
Ctrl+Z— UndoCtrl+Y/Ctrl+Shift+Z— RedoDel/Backspace— Delete selected elements
- Zoom controls: zoom in/out, reset, fit-to-screen
- Save persists to Frappe database
- HTML entity decoding for stored XML
- SpiffWorkflow properties panel extensions via forked
bpmn-js-spiffworkflow - Script editor launch for Script Tasks, Pre/Post scripts
- Markdown editor launch for User Task / Manual Task instructions
- Call Activity editor launch for Call Activity elements
- Event bus handlers for SpiffWorkflow data requests (service tasks, JSON schemas, DMN files, data stores, messages)
- Loop data reference fix for multi-instance activities
- Create and manage shape libraries via the sidebar panel
- Upload custom SVG shapes to libraries
- Drag and drop custom shapes onto the BPMN canvas
- Custom shapes render as BPMN Tasks with embedded SVG
- Per-element text formatting (font family, size, weight, style, decoration, alignment)
- Custom moddle extension persists styles as XML attributes (
custom:fontFamily,custom:fontSize, etc.) - Custom renderer applies styles during diagram rendering
- Custom translate module for localizing BPMN palette, context pad, and properties panel labels
- Creates new diagram with empty start event
- Links diagram to parent Process via
process_name
| Package | Version | Purpose |
|---|---|---|
bpmn-js |
^17.11.1 | BPMN modeler core |
bpmn-js-properties-panel |
^5.48.0 | Properties panel for BPMN elements |
bpmn-js-spiffworkflow |
forked | SpiffWorkflow extensions (ESM build) |
@bpmn-io/properties-panel |
^3.36.0 | Base properties panel framework |
frappe-ui |
0.1.192 | Frappe UI components (Tooltip, TextEditor, Dialog) |
vue |
^3.5.13 | Vue.js framework |
vue-router |
^4.5.0 | Client-side routing |
dayjs |
^1.11.7 | Date/time formatting |
@iconify/vue |
^5.0.0 | Icon component (Lucide icons) |
diagram-js-minimap |
^5.2.0 | Minimap module (currently disabled) |
bpmn-js-color-picker |
^0.7.2 | Color picker integration |
tailwindcss |
^3.4.17 | Utility-first CSS (dev) |
cd apps/one_bpmn/spiff
yarn build
cd ../../../
bench build --app one_bpmn
bench --site your-site.local clear-cache- API endpoint: Add to
one_bpmn/api.pywith@frappe.whitelist() - Vue component: Add to
spiff/src/components/ - Page/view: Add to
spiff/src/views/and register inrouter/index.js - bpmn-js module: Add to
spiff/src/bpmn/,spiff/src/renderers/, orspiff/src/rules/and register inBpmnEditor.vue'sadditionalModules - Moddle extension: Add to
spiff/src/moddle/and register inBpmnEditor.vue'smoddleExtensions
- Run
bench build --app one_bpmn - Clear cache:
bench --site your-site.local clear-cache - Hard refresh browser (Ctrl+Shift+R)
Restart the server after adding new API methods:
bench restartCheck browser console for errors. Common fixes:
- Ensure bpmn-js CSS is imported
- Verify container element has proper dimensions
The app automatically decodes HTML entities in stored XML. If issues persist, check browser console for the decoded XML output.
- Verify
bpmn-js-spiffworkflowis installed:ls node_modules/bpmn-js-spiffworkflow/ - Ensure the
spiffworkflowmodule andspiffModdleExtensionare registered inBpmnEditor.vue - Check browser console for import errors
ProsAlly is an AI-powered chat assistant embedded in the BPMN editor that helps users create, overwrite, and modify process diagrams through natural language prompts.
ProsAlly lives as a collapsible side panel (420 px wide on desktop, full-height bottom sheet on mobile) inside the BPMN editor. It runs a multi-step agent pipeline:
User prompt
│
▼
Intent Classifier
│
├─ GENERATE_NEW / OVERWRITE_EXISTING / MODIFY_EXISTING
│ │
│ ▼
│ Confirmer ──► "I'll draw … Shall I proceed?" + [Yes / No, let me adjust]
│ │ (confirmed)
│ ▼
│ Generator / Modifier ──► BPMN 2.0 XML ──► canvas
│
├─ AMBIGUOUS / INCOMPLETE
│ │
│ ▼
│ Clarifier ──► focused question + option buttons
│
└─ IRRELEVANT ──► polite redirect
| Intent | Trigger | ProsAlly response |
|---|---|---|
GENERATE_NEW |
User wants a brand-new process on an empty canvas | Confirms, then generates BPMN |
OVERWRITE_EXISTING |
User wants to replace the existing model entirely | Confirms, then regenerates |
MODIFY_EXISTING |
User wants to add/remove/change a specific part | Confirms, then patches XML |
AMBIGUOUS |
Multiple valid interpretations | Asks a clarifying question with options |
INCOMPLETE |
Clear intent but missing details | Asks for the missing information |
IRRELEVANT |
Nothing to do with process modelling | Redirects politely |
| File | Purpose |
|---|---|
spiff/src/components/ProsAllyPanel.vue |
Chat UI — messages, option buttons, rich-text input |
spiff/src/components/BpmnEditor.vue |
Panel integration, toggle button, mobile bottom-sheet |
spiff/src/utils/bpmnLayout.js |
Auto-layout algorithm — positions generated BPMN elements left-to-right |
spiff/src/linting/bpmnlintrc.js |
bpmnlint rule configuration (errors vs. warnings) |
one_bpmn/api.py → prosally_chat |
Frappe API endpoint — orchestrates the agent pipeline |
one_bpmn/agents/google_adk/prosally_agent/prosally_agent.py |
LLM agent — intent classification, clarification, generation, modification |
one_bpmn/utils/chat_persistence.py |
Persists conversation history in Chat Conversation DocType |
The ProsAllyAgent class in prosally_agent.py runs fully async:
- IntentClassifier — calls the LLM with a structured prompt, expects
{"intent": "...", "reason": "..."}. - Clarifier — for
AMBIGUOUS/INCOMPLETE, returns{"question": "...", "options": [...]}. - Confirmer — for actionable intents, returns
{"summary": "...", "question": "..."}. - Generator — produces complete BPMN 2.0 XML from scratch.
- Modifier — receives existing canvas XML and a patch instruction, returns updated XML.
All LLM responses are parsed via _parse_json_response() which strips markdown code fences before calling json.loads, with a fallback that extracts the first {...} block from the raw response.
When ProsAlly generates or modifies a diagram, the raw XML uses placeholder coordinates. The layout function:
- Uses
DOMParserread-only to extract element types, IDs, and sequence flow connections. - Runs a BFS + join-relaxation algorithm to assign left-to-right column positions.
- Builds the entire
<bpmndi:BPMNDiagram>section as a plain string with hardcoded namespace prefixes — never usingXMLSerializer— to prevent namespace prefix mangling that causes bpmn-moddle reference resolution failures. - Reads the actual
<bpmn:process id="...">value and uses it asBPMNPlane bpmnElement, ensuring it always matches. - Assigns correct element dimensions: events 36×36, gateways 50×50, tasks 100×80.
- Adds
isMarkerVisible="true"on exclusive gateway shapes. - Routes edges with straight connectors (same row), Manhattan L-routing (different rows), and top-arc routing (back-edges / loop-backs).
The generator LLM is instructed to produce BPMN 2.0 that satisfies all active bpmnlint rules:
- Fixed
id="Process_1"on<bpmn:process>and matchingbpmnElement="Process_1"on<bpmndi:BPMNPlane>— eliminatesno-bpmndierrors from ID mismatch. - Exclusive gateways use a
default="Flow_..."attribute on the gateway and<bpmn:conditionExpression>on every non-default outgoing flow — eliminatesconditional-flowswarnings. - Separate DI placeholder dimensions per element type match the layout algorithm's
DIMStable.
ProsAlly's LLM provider, model, and API key are configured per-agent in the Processa Agent LLM Config child table of the AI Agent Configuration DocType. The agent_id must match "prosally_agent". Supported providers: anthropic, gemini, openai.
Each chat session is stored as a Chat Conversation record with agent_mode="ProsAlly". The last 30 messages are loaded as context on each turn. The conversation_name is returned from the backend and stored in the frontend session, enabling multi-turn coherent conversations.
| Rule | Severity | Description |
|---|---|---|
start-event-required |
Error | Process must have a start event |
end-event-required |
Error | Process must have an end event |
no-disconnected |
Error | All elements must have at least one connection |
single-blank-start-event |
Error | Exactly one blank (untyped) start event |
no-bpmndi |
Error | Every semantic element must have DI (BPMNShape/BPMNEdge) |
conditional-flows |
Warning | Gateway outgoing flows must have conditions or a default |
no-overlapping-elements |
Warning | Shapes must not overlap |
label-required |
Warning | Elements should have names |
no-implicit-split |
Warning | Tasks should not have multiple outgoing flows |
superfluous-gateway |
Warning | Gateways with only one path |
no-gateway-join-fork |
Warning | Gateways should not both join and fork |
Generate new process:
"Create a leave request approval process. It starts when an employee submits a leave request. A line manager reviews it and either approves or rejects it. If approved, HR updates the records and the process ends. If rejected, the employee is notified and the process ends."
Modify existing process (canvas must have a diagram loaded):
"Add a 'Send Confirmation Email' task after the 'Approve Request' step."
Overwrite existing:
"Scrap the current model and redraw the entire process from scratch based on the new SOP."
MIT
Run the full app test suite from bench:
bench --site <site> run-tests --app one_bpmn --failfast- Branch from
staging - Keep changes scoped to the work item
- Open PRs back to
staging - Prefer small, reviewable changes
one_bpmn/api.pyexposes backend endpoints for BPMN model managementone_bpmn/tasks.pyhandles scheduled timer-related processingone_bpmn/one_bpmn/doctype/contains BPMN doctypes and server logicspiff/contains the frontend BPMN editor
Primary concepts:
- Process Model
- Process Instance
- Activity Log
- Shape Library
- Custom Shape