frappe-impl-workflow — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited frappe-impl-workflow (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Step-by-step guide for implementing document workflows in Frappe. Covers design, setup, testing, and common approval chain patterns.
1. □ Design states and transitions on paper/diagram first
2. □ Create Workflow State records (master list)
3. □ Create Workflow Action Master records (Approve, Reject, etc.)
4. □ Create the Workflow DocType record
5. □ Add states with correct doc_status values
6. □ Add transitions with roles, actions, and conditions
7. □ Set allow_edit roles per state
8. □ Configure email notifications (optional)
9. □ Test every transition path with test users
10. □ Verify self-approval blocking works as expectedBefore touching the UI, map out your workflow on paper.
ALWAYS start by listing every distinct document stage:
Example — Purchase Order Approval:
Draft → Pending Review → Pending Approval → Approved → Submitted → CancelledFor submittable DocTypes, ALWAYS assign doc_status correctly:
| Stage | doc_status | Meaning |
|---|---|---|
| All "in-progress" states | 0 | Document is Draft, editable |
| Final approved/active state | 1 | Document is Submitted, locked |
| Cancelled state | 2 | Document is Cancelled |
NEVER assign doc_status = 1 to intermediate approval states. A submitted document cannot return to draft. Once submitted, the only forward path is another submitted state or cancellation.
For each state, define: What actions are possible? Who can perform them? Any conditions?
Draft →[Submit for Review / Creator]→ Pending Review
Pending Review →[Approve / Reviewer]→ Pending Approval
Pending Review →[Reject / Reviewer]→ Draft
Pending Approval →[Approve / Manager]→ Approved
Pending Approval →[Reject / Manager]→ Draft
Approved →[Submit / Manager]→ Submitted (doc_status=1)
Submitted →[Cancel / Manager]→ Cancelled (doc_status=2)Navigate to Workflow State list or create via API:
# Create states with appropriate styles
states = [
{"workflow_state_name": "Draft", "style": ""},
{"workflow_state_name": "Pending Review", "style": "Primary"},
{"workflow_state_name": "Pending Approval", "style": "Warning"},
{"workflow_state_name": "Approved", "style": "Success"},
{"workflow_state_name": "Submitted", "style": "Info"},
{"workflow_state_name": "Rejected", "style": "Danger"},
{"workflow_state_name": "Cancelled", "style": "Inverse"},
]
for s in states:
if not frappe.db.exists("Workflow State", s["workflow_state_name"]):
frappe.get_doc({"doctype": "Workflow State", **s}).insert()Available styles: Primary, Success, Warning, Danger, Info, Inverse (or empty for default).
actions = ["Submit for Review", "Approve", "Reject", "Send Back", "Cancel"]
for action in actions:
if not frappe.db.exists("Workflow Action Master", action):
frappe.get_doc({
"doctype": "Workflow Action Master",
"workflow_action_name": action
}).insert()Navigate to Setup > Workflow > New Workflow:
workflow = frappe.get_doc({
"doctype": "Workflow",
"workflow_name": "Purchase Order Approval",
"document_type": "Purchase Order",
"is_active": 1,
"send_email_alert": 1,
"states": [
{"state": "Draft", "doc_status": "0", "allow_edit": "Purchase User"},
{"state": "Pending Approval", "doc_status": "0", "allow_edit": "Purchase Manager"},
{"state": "Approved", "doc_status": "1", "allow_edit": "Purchase Manager"},
{"state": "Rejected", "doc_status": "0", "allow_edit": "Purchase User"},
{"state": "Cancelled", "doc_status": "2"},
],
"transitions": [
{
"state": "Draft",
"action": "Submit for Review",
"next_state": "Pending Approval",
"allowed": "Purchase User",
"allow_self_approval": 1,
},
{
"state": "Pending Approval",
"action": "Approve",
"next_state": "Approved",
"allowed": "Purchase Manager",
"allow_self_approval": 0,
},
{
"state": "Pending Approval",
"action": "Reject",
"next_state": "Rejected",
"allowed": "Purchase Manager",
},
{
"state": "Rejected",
"action": "Submit for Review",
"next_state": "Pending Approval",
"allowed": "Purchase User",
},
{
"state": "Approved",
"action": "Cancel",
"next_state": "Cancelled",
"allowed": "Purchase Manager",
},
],
})
workflow.insert()Add Python conditions to show transitions only when criteria are met:
# Only allow approval for orders above 50000 by Senior Manager
{
"state": "Pending Approval",
"action": "Approve",
"next_state": "Approved",
"allowed": "Senior Manager",
"condition": "doc.grand_total > 50000",
}
# Standard approval for orders up to 50000
{
"state": "Pending Approval",
"action": "Approve",
"next_state": "Approved",
"allowed": "Purchase Manager",
"condition": "doc.grand_total <= 50000",
}ALWAYS use doc.fieldname syntax in conditions (the document is exposed as a dict).
Available in conditions: frappe.db.get_value(), frappe.db.get_list(), frappe.session.user, frappe.utils.now_datetime(), frappe.utils.add_to_date(), frappe.utils.get_datetime().
Set allow_self_approval = 0 on approval transitions. This means:
send_email_alert = 1 on the Workflowsend_email = 1 (default)Email Template via next_action_email_templatemessage on the state row for inline notification textUse update_field and update_value on state rows to automatically set document fields:
# Set approval_status when entering "Approved" state
{"state": "Approved", "doc_status": "1",
"update_field": "approval_status", "update_value": "Approved"}
# Use expression to set approval date dynamically
{"state": "Approved", "doc_status": "1",
"update_field": "custom_approved_on", "update_value": "frappe.utils.now()",
"evaluate_as_expression": 1}# Get available transitions for a document
from frappe.model.workflow import get_transitions, apply_workflow
doc = frappe.get_doc("Purchase Order", "PO-00001")
transitions = get_transitions(doc)
# Returns list of dicts with action, next_state, allowed, etc.
# Apply a workflow action
updated_doc = apply_workflow(doc, "Approve")
# Returns the updated document after state changeDraft → Level 1 Review → Level 2 Review → Approved → SubmittedEach level has its own role. Document moves linearly through approvals.
Draft → Pending Approval
├─[amount <= 10000 / Team Lead]─→ Approved
├─[amount <= 50000 / Manager]──→ Approved
└─[amount > 50000 / Director]──→ ApprovedUse condition on each transition to route based on document values.
Draft ←──[Reject]── Pending Review ──[Approve]──→ Approved
└──[Submit for Review]──→ Pending ReviewRejected documents return to Draft for revision. Creator resubmits. This is the most common approval pattern.
Applied (doc_status=0, allow_edit=Employee)
└─[Approve / Leave Approver]─→ Approved (doc_status=1)
└─[Reject / Leave Approver]─→ Rejected (doc_status=0)
Approved
└─[Cancel / HR Manager]─→ Cancelled (doc_status=2)For non-submittable DocTypes, ALL states MUST have doc_status = 0:
Draft → Under Review → Reviewed → Published
(all doc_status = 0)If your DocType currently uses manual Submit/Cancel buttons and you want to add a workflow:
docstatus when the workflow is createdoverride_status, the workflow state replaces the Status columnNEVER activate a workflow without a state for docstatus=0. New documents would have no valid initial state.
Starting a new workflow implementation?
│
├── What type of DocType?
│ ├── Submittable → can use doc_status 0, 1, 2
│ └── Non-submittable → ALL states must be doc_status = 0
│
├── How many approval levels?
│ ├── Single → Two-state: Draft → Approved
│ ├── Sequential → Chain: Draft → L1 → L2 → Approved
│ └── Conditional → Route by field values using conditions
│
├── Need self-approval blocking?
│ └── Set allow_self_approval = 0 on approval transitions
│
├── Need rejection/revision loop?
│ └── Add Reject transition back to Draft or previous state
│
├── Need email notifications?
│ ├── Enable send_email_alert on Workflow
│ └── Link Email Template on each state
│
└── Need automated field updates?
└── Use update_field + update_value on target state| Problem | Cause | Solution |
|---|---|---|
| No action buttons visible | User lacks required role | Add role to user OR add transition for user's role |
| "Self approval is not allowed" | User is doc owner + allow_self_approval=0 | Have different user approve, or set flag to 1 |
| "Workflow State not set" | Document created before workflow activation | Run update_default_workflow_status or manually set state |
| Document stuck in state | No outgoing transition defined for current state + user role | Add missing transition |
| "Cannot cancel before submitting" | Transition goes from doc_status=0 to doc_status=2 | Add intermediate submitted state |
| Actions show for wrong users | Role assignment too broad | Use more specific roles or add conditions |
frappe-core-workflow — Workflow engine internals and API reference~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.