Skip to main content
ERPNext provides comprehensive automation tools to streamline business operations, reduce manual work, and ensure consistency across processes.

Workflow Automation

Define multi-step approval processes with custom rules and conditions.

Creating Workflows

1

Define Workflow

Navigate to Setup > Workflow > Workflow
workflow = frappe.get_doc({
    "doctype": "Workflow",
    "workflow_name": "Sales Order Approval",
    "document_type": "Sales Order",
    "is_active": 1,
    "workflow_state_field": "workflow_state"
})
2

Add States

Define the stages your document goes through:
  • Draft → Pending Approval → Approved → Rejected
  • Each state can have different allowed roles
  • Set colors for visual indication
3

Configure Transitions

Define allowed transitions between states:
transition = {
    "state": "Draft",
    "action": "Submit for Approval",
    "next_state": "Pending Approval",
    "allowed": "Sales User",
    "condition": "doc.grand_total > 10000"
}
4

Set Conditions

Add business rules:
# Condition examples
"doc.grand_total > 100000"  # Amount threshold
"doc.customer_group == 'VIP'"  # Customer category
"doc.territory in ['North', 'South']"  # Geographic rules

Workflow Actions

Approval

Route documents through approval hierarchy based on amount, department, or custom criteria.

Rejection

Send back to previous state with comments and reasons.

Conditional Routing

Automatic routing based on field values or calculations.

Parallel Approval

Multiple approvers can review simultaneously.

Scheduled Events

Automate recurring tasks using the scheduler:
# hooks.py - Register scheduled events
scheduler_events = {
    "hourly": [
        "erpnext.projects.doctype.project.project.hourly_reminder"
    ],
    "hourly_long": [
        "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
    ],
    "hourly_maintenance": [
        "erpnext.utilities.bulk_transaction.retry",
        "erpnext.projects.doctype.project.project.collect_project_status"
    ]
}

Common Scheduled Tasks

Automatically create Purchase Requests when inventory falls below reorder level:
def reorder_item():
    items = frappe.db.sql("""
        SELECT item_code, warehouse, reorder_level, reorder_qty
        FROM `tabItem Reorder`
        WHERE enabled = 1
    """, as_dict=1)
    
    for item in items:
        current_qty = get_stock_qty(item.item_code, item.warehouse)
        if current_qty < item.reorder_level:
            create_material_request(item)
Send automated reminders for overdue invoices:
def send_payment_reminders():
    overdue_invoices = frappe.db.sql("""
        SELECT name, customer, due_date, outstanding_amount
        FROM `tabSales Invoice`
        WHERE docstatus = 1 
          AND outstanding_amount > 0
          AND due_date < CURDATE()
    """, as_dict=1)
    
    for invoice in overdue_invoices:
        send_reminder_email(invoice)
Archive old records and clean up temporary data:
def cleanup_old_logs():
    # Delete old activity logs
    frappe.db.sql("""
        DELETE FROM `tabActivity Log`
        WHERE creation < DATE_SUB(NOW(), INTERVAL 90 DAY)
    """)
    
    # Archive old documents
    old_docs = frappe.get_all(
        "Sales Order",
        filters={"creation": ["<", "2020-01-01"]},
        pluck="name"
    )
    for doc in old_docs:
        archive_document("Sales Order", doc)

Email Automation

Email Alerts

Trigger emails based on document events:
# Setup > Email > Email Alert
email_alert = frappe.get_doc({
    "doctype": "Email Alert",
    "subject": "New Sales Order: {{ doc.name }}",
    "document_type": "Sales Order",
    "event": "Submit",
    "send_to_all_assignees": 1,
    "recipients": [
        {"email_by_document_field": "sales_manager_email"}
    ],
    "condition": "doc.grand_total > 50000",
    "message": """
        <p>A new high-value order has been placed:</p>
        <ul>
            <li>Customer: {{ doc.customer }}</li>
            <li>Amount: {{ doc.grand_total }}</li>
            <li>Delivery Date: {{ doc.delivery_date }}</li>
        </ul>
    """
})
email_alert.insert()
Trigger Events:
  • New document created
  • Document saved
  • Document submitted
  • Document cancelled
  • Value changed (specific field)
  • Days before/after date field
  • Custom conditions

Email Campaigns

Automated marketing campaigns:
# CRM > Email Campaign
campaign = frappe.get_doc({
    "doctype": "Email Campaign",
    "name": "Lead Nurture Series",
    "campaign_name": "Product Launch",
    "email_campaign_for": "Lead",
    "sender": "[email protected]",
    "status": "Active"
})

# Add email schedules
campaign.append("campaign_schedules", {
    "send_after_days": 0,
    "email_template": "Welcome Email"
})
campaign.append("campaign_schedules", {
    "send_after_days": 3,
    "email_template": "Feature Introduction"
})
campaign.append("campaign_schedules", {
    "send_after_days": 7,
    "email_template": "Special Offer"
})
campaign.insert()

Auto-Repeat (Subscriptions)

Automate recurring transactions:
# Create auto-repeat for monthly invoices
auto_repeat = frappe.get_doc({
    "doctype": "Auto Repeat",
    "reference_doctype": "Sales Invoice",
    "reference_document": "SINV-00001",
    "frequency": "Monthly",
    "start_date": "2024-01-01",
    "end_date": "2024-12-31",
    "repeat_on_day": 1,  # 1st of each month
    "notify_by_email": 1,
    "recipients": "[email protected]",
    "submit_on_creation": 1
})
auto_repeat.insert()
Use Cases:
  • Monthly subscriptions
  • Recurring rentals
  • Annual maintenance contracts
  • Periodic purchase orders
  • Regular salary payments

Document Events & Hooks

Trigger custom code on document lifecycle events:
# hooks.py
doc_events = {
    "Sales Invoice": {
        "before_insert": "your_app.custom.validate_customer",
        "after_insert": "your_app.custom.notify_sales_team",
        "before_save": "your_app.custom.calculate_commission",
        "on_submit": "your_app.custom.update_inventory",
        "on_cancel": "your_app.custom.reverse_inventory",
        "on_trash": "your_app.custom.cleanup_related_docs"
    }
}

Assignment Automation

Auto-assign documents to users:
# Setup > Automation > Assignment Rule
assignment_rule = frappe.get_doc({
    "doctype": "Assignment Rule",
    "name": "Auto Assign Leads",
    "document_type": "Lead",
    "assign_condition": "doc.status == 'Open'",
    "rule": "Round Robin",  # or "Load Balancing", "Based on Field"
    "users": [
        {"user": "[email protected]"},
        {"user": "[email protected]"},
        {"user": "[email protected]"}
    ]
})
assignment_rule.insert()
Assignment Strategies:
  • Round Robin: Distribute evenly in rotation
  • Load Balancing: Assign to user with fewest open documents
  • Based on Field: Assign based on territory, department, etc.
  • Priority Based: Assign high-priority items to specific users

Notification Automation

System Notifications

In-app notifications for document assignments, mentions, and updates.

SMS Alerts

Send SMS for critical events like payment confirmations.

Push Notifications

Mobile app notifications for mobile users.

Slack/Teams

Integration with team collaboration tools.

Bank Reconciliation Automation

Automatic matching of bank transactions:
# accounts/doctype/bank_transaction/auto_match_party.py
def auto_match_party():
    # Get unreconciled transactions
    transactions = frappe.get_all(
        "Bank Transaction",
        filters={"status": "Unreconciled"},
        fields=["name", "description", "withdrawal", "deposit"]
    )
    
    for transaction in transactions:
        # Try to match with invoices
        matches = find_matching_invoices(transaction)
        
        if len(matches) == 1:
            # Auto-reconcile if single match
            reconcile_transaction(transaction, matches[0])

API Webhooks

Trigger external systems when documents change:
# Setup > Integrations > Webhook
webhook = frappe.get_doc({
    "doctype": "Webhook",
    "webhook_doctype": "Sales Order",
    "webhook_docevent": "on_submit",
    "request_url": "https://api.external-system.com/order-created",
    "request_method": "POST",
    "request_structure": "Form URL-Encoded",
    "webhook_headers": [
        {"key": "Authorization", "value": "Bearer TOKEN"},
        {"key": "Content-Type", "value": "application/json"}
    ],
    "webhook_data": [
        {"fieldname": "name", "key": "order_id"},
        {"fieldname": "customer", "key": "customer_name"},
        {"fieldname": "grand_total", "key": "total_amount"}
    ]
})
webhook.insert()

Server Scripts

Write custom automation without deploying apps:
// Setup > Automation > Server Script
// Script Type: DocType Event
// DocType: Sales Order
// Event: Before Save

if (doc.customer) {
    let customer = frappe.get_doc("Customer", doc.customer);
    
    // Apply customer-specific discount
    if (customer.discount_percentage) {
        doc.additional_discount_percentage = customer.discount_percentage;
    }
    
    // Set payment terms
    if (customer.payment_terms) {
        doc.payment_terms_template = customer.payment_terms;
    }
}

Best Practices

Automation Guidelines
  • Test automations in staging before production
  • Use error handling and logging
  • Avoid infinite loops in document events
  • Set appropriate permissions for automated actions
  • Monitor automated jobs for failures
  • Use queues for long-running tasks
  • Document all automation rules

Performance

  • Use background jobs for bulk operations
  • Implement rate limiting
  • Cache frequently accessed data
  • Optimize database queries

Reliability

  • Add retry logic with exponential backoff
  • Log all automation activities
  • Send failure notifications
  • Use dead letter queues

Next Steps

Reporting

Automate report generation and distribution

Integrations

Connect with external systems

Build docs developers (and LLMs) love