Documentation Index
Fetch the complete documentation index at: https://mintlify.com/frappe/frappe/llms.txt
Use this file to discover all available pages before exploring further.
Frappe Framework provides a powerful workflow engine that allows you to define multi-step approval processes for documents.
What are workflows?
Workflows define a set of states and transitions that a document can go through, with optional approval requirements and conditions.
Key components:
- States: Different stages a document can be in (e.g., Draft, Pending Approval, Approved)
- Transitions: Rules for moving between states
- Actions: User actions that trigger transitions
- Conditions: Optional conditions that must be met for transitions
Creating a workflow
Create a workflow for a DocType:
import frappe
# Create workflow
workflow = frappe.get_doc({
"doctype": "Workflow",
"workflow_name": "Leave Application Approval",
"document_type": "Leave Application",
"is_active": 1,
"workflow_state_field": "workflow_state",
"states": [
{
"state": "Draft",
"doc_status": "0",
"allow_edit": "Employee"
},
{
"state": "Pending Approval",
"doc_status": "0",
"allow_edit": "Leave Approver"
},
{
"state": "Approved",
"doc_status": "1",
"allow_edit": "Leave Approver"
},
{
"state": "Rejected",
"doc_status": "2"
}
],
"transitions": [
{
"state": "Draft",
"action": "Submit",
"next_state": "Pending Approval",
"allowed": "Employee"
},
{
"state": "Pending Approval",
"action": "Approve",
"next_state": "Approved",
"allowed": "Leave Approver"
},
{
"state": "Pending Approval",
"action": "Reject",
"next_state": "Rejected",
"allowed": "Leave Approver"
}
]
}).insert()
Workflow states
Define the states a document can be in:
{
"state": "Pending Approval",
"doc_status": "0", # 0=Draft, 1=Submitted, 2=Cancelled
"allow_edit": "Leave Approver", # Role allowed to edit
"update_field": "status", # Field to update
"update_value": "Pending" # Value to set
}
State properties
state: Name of the state
doc_status: Document status (0=Draft, 1=Submitted, 2=Cancelled)
allow_edit: Role allowed to edit in this state
update_field: Optional field to update when entering state
update_value: Value to set in update_field
Workflow transitions
Define how documents move between states:
{
"state": "Pending Approval",
"action": "Approve",
"next_state": "Approved",
"allowed": "Leave Approver",
"condition": "doc.leave_days <= 5"
}
Transition properties
state: Current state
action: Action name shown to user
next_state: State to transition to
allowed: Role allowed to perform action
condition: Optional Python expression to evaluate
action_method: Optional method to execute on transition
Conditional transitions
Add conditions to transitions:
{
"state": "Pending Approval",
"action": "Auto Approve",
"next_state": "Approved",
"allowed": "Leave Approver",
"condition": "doc.leave_days <= 2 and doc.leave_balance >= doc.leave_days"
}
Conditions can access:
doc: The document object
frappe: The frappe module
Workflow actions
Execute custom code on transitions:
# In DocType controller
class LeaveApplication(Document):
def on_workflow_action(self, action):
"""Called when workflow action is performed"""
if action == "Approve":
self.send_approval_notification()
elif action == "Reject":
self.send_rejection_notification()
def send_approval_notification(self):
frappe.sendmail(
recipients=[self.employee_email],
subject=f"Leave Application {self.name} Approved",
message="Your leave application has been approved."
)
Workflow state field
The workflow automatically creates a custom field to store the current state:
# Access workflow state
doc = frappe.get_doc("Leave Application", "LA-001")
print(doc.workflow_state) # "Pending Approval"
# Check state
if doc.workflow_state == "Approved":
process_approved_leave(doc)
Multiple approvers
Implement multi-level approval:
workflow = frappe.get_doc({
"doctype": "Workflow",
"workflow_name": "Purchase Order Approval",
"document_type": "Purchase Order",
"states": [
{"state": "Draft", "doc_status": "0"},
{"state": "Pending Manager Approval", "doc_status": "0"},
{"state": "Pending Director Approval", "doc_status": "0"},
{"state": "Approved", "doc_status": "1"}
],
"transitions": [
{
"state": "Draft",
"action": "Submit",
"next_state": "Pending Manager Approval",
"allowed": "Employee"
},
{
"state": "Pending Manager Approval",
"action": "Approve",
"next_state": "Pending Director Approval",
"allowed": "Manager",
"condition": "doc.grand_total > 10000"
},
{
"state": "Pending Manager Approval",
"action": "Approve",
"next_state": "Approved",
"allowed": "Manager",
"condition": "doc.grand_total <= 10000"
},
{
"state": "Pending Director Approval",
"action": "Approve",
"next_state": "Approved",
"allowed": "Director"
}
]
})
Email notifications
Send email alerts on workflow actions:
# Enable in workflow
workflow.send_email_alert = 1
workflow.save()
# Create email alert
email_alert = frappe.get_doc({
"doctype": "Email Alert",
"subject": "Leave Application Pending Approval",
"document_type": "Leave Application",
"event": "Workflow State Change",
"enabled": 1,
"recipients": [
{"email_by_document_field": "leave_approver_email"}
],
"message": """
<p>A leave application is pending your approval:</p>
<p>Employee: {{ doc.employee_name }}</p>
<p>From: {{ doc.from_date }}</p>
<p>To: {{ doc.to_date }}</p>
<p>Days: {{ doc.total_leave_days }}</p>
"""
}).insert()
Workflow permissions
Control who can perform actions:
# Check if user can perform action
from frappe.model.workflow import can_apply_workflow
if can_apply_workflow(doc):
# Show workflow actions
pass
# Get allowed actions for user
from frappe.model.workflow import get_transitions
transitions = get_transitions(doc)
for transition in transitions:
print(f"Allowed action: {transition['action']}")
Workflow state count
Get count of documents in each state:
from frappe.workflow.doctype.workflow.workflow import get_workflow_state_count
states_to_exclude = ["Approved", "Rejected"]
counts = get_workflow_state_count(
"Leave Application",
"workflow_state",
states_to_exclude
)
# Returns: [{"workflow_state": "Pending Approval", "count": 5}]
Custom workflow methods
Define custom methods for workflow actions:
# In hooks.py
workflow_methods = [
"myapp.workflows.custom_approval_method"
]
# Custom method
def custom_approval_method(doc, transition):
"""Custom logic for workflow transition"""
if transition.action == "Approve":
# Custom approval logic
doc.approval_date = frappe.utils.nowdate()
doc.approved_by = frappe.session.user
Bypassing workflow
Bypass workflow for specific scenarios:
import frappe
def update_without_workflow(doctype, name, field, value):
"""Update document without triggering workflow"""
doc = frappe.get_doc(doctype, name)
# Bypass workflow
doc.flags.ignore_workflow = True
doc.set(field, value)
doc.save()
Workflow validation
Validate workflow configuration:
class Workflow(Document):
def validate_docstatus(self):
"""Validate workflow state docstatus values"""
# Cannot change from cancelled state
# Cannot go from draft to cancelled
# Must submit before cancelling
for transition in self.transitions:
state = get_state(transition.state)
next_state = get_state(transition.next_state)
if state.doc_status == 2:
frappe.throw("Cannot change state of Cancelled Document")
if state.doc_status == 0 and next_state.doc_status == 2:
frappe.throw("Cannot cancel before submitting")
Workflow best practices
- Keep it simple: Don’t create overly complex workflows
- Use conditions: Add conditions to prevent invalid transitions
- Email notifications: Keep users informed of workflow progress
- Permission roles: Use specific roles for different approval levels
- Test thoroughly: Test all possible paths through the workflow
- Document processes: Maintain documentation of workflow logic
- Monitor performance: Track workflow bottlenecks and delays