Back to Blogs
Frappe Framework

Frappe Dev Concepts: Documents, Methods & Events

July 2, 202515 min read

Introduction

A Document is an instance of a DocType in Frappe. It is derived from the frappe.model.Document class and represents a single record in the database table. This guide covers the essential Frappe Python API for working with documents.

Fetching Documents

1. frappe.get_doc

Returns a Document object of the record identified by doctype and name. If no document is found, a DoesNotExistError is raised.

python
# get an existing document
doc = frappe.get_doc('Task', 'TASK00002')
doc.title = 'Test'
doc.save()

# get a single doctype
doc = frappe.get_doc('System Settings')
doc.timezone  # Asia/Kolkata

2. frappe.get_doc(dict)

Returns a new Document object in memory which does not exist yet in the database.

python
# create a new document
doc = frappe.get_doc({
    'doctype': 'Task',
    'title': 'New Task'
})
doc.insert()

# create with keyword arguments
user = frappe.get_doc(doctype='User', email_id='test@example.com')
user.insert()

3. frappe.get_last_doc

Returns the last Document object created under the mentioned doctype. Supports filters and custom ordering.

python
# get the last Task created
task = frappe.get_last_doc('Task')

# get the last Cancelled Task
task = frappe.get_last_doc('Task', filters={"status": "Cancelled"})

# custom ordering
task = frappe.get_last_doc('Task', filters={"Status": "Cancelled"}, order_by="timestamp desc")

4. frappe.new_doc

Alternative way to create a new Document.

python
doc = frappe.new_doc('Task')
doc.title = 'New Task 2'
doc.insert()

5. frappe.get_docs

Returns a list of Document objects. Use as_iterator=True to fetch records in chunks for better memory management.

python
# Fetch specific documents
tasks = frappe.get_docs('Task', filters={'status': 'Open'}, limit=10)
for task in tasks:
    task.status = "Closed"
    task.save()

# Efficiently iterate through large datasets
leads = frappe.get_docs('Lead', as_iterator=True, chunk_size=500)
for lead in leads:
    lead.process_lead()

6. frappe.delete_doc & frappe.rename_doc

python
# Delete a document
frappe.delete_doc('Task', 'TASK00002')

# Rename a document (Allow Rename must be set in DocType)
frappe.rename_doc('Task', 'TASK00002', 'TASK00003')

# Rename with merge
frappe.rename_doc('Task', 'TASK00002', 'TASK00003', merge=True)

7. frappe.get_meta

Returns meta information of a doctype including custom fields and property setters.

python
meta = frappe.get_meta('Task')
meta.has_field('status')  # True
meta.get_custom_fields()  # [field1, field2, ..]

Document Methods

doc.insert

Inserts a new document into the database. Runs before_insert, validate, on_update, after_insert controller methods.

python
doc.insert(
    ignore_permissions=True,
    ignore_links=True,
    ignore_if_duplicate=True,
    ignore_mandatory=True
)

doc.save

Saves changes to an existing document. Executes validate before updating and on_update after.

python
doc.save(
    ignore_permissions=True,
    ignore_version=True  # do not create a version record
)

doc.db_set

Set a field value directly in the database. Does NOT trigger controller validations — use carefully.

python
# updates value, updates modified timestamp
doc.db_set('price', 2300)

# also triggers notify_update
doc.db_set('price', 2300, notify=True)

# also runs frappe.db.commit()
doc.db_set('price', 2300, commit=True)

# skip modified timestamp update
doc.db_set('price', 2300, update_modified=False)

doc.append

Append a new row to a child table.

python
doc.append("items", {
    "item_code": "ITEM-001",
    "qty": 5,
    "rate": 100
})

Other Useful Methods

python
# Check if a value changed before save
if doc.has_value_changed("price"):
    pass

# Get the doc state before current changes
old_doc = doc.get_doc_before_save()

# Reload from database
doc.reload()

# Get desk URL
url = doc.get_url()  # /app/task/TASK00002

# Add a comment
doc.add_comment('Comment', text='Test Comment')

# Run a controller method in background
doc.queue_action('send_emails', emails=email_list, message='Howdy')

Document Events (doc_events)

Frappe provides doc_events in hooks.py to attach custom Python functions to document lifecycle events — without modifying core code.

python
# hooks.py
doc_events = {
    "Sales Invoice": {
        "on_submit": "my_app.invoice.on_submit"
    }
}

# The handler function
def on_submit(doc, method):
    update_loyalty_points(doc.customer)

Event Lifecycle

  • before_insert — Document exists only in memory. Set defaults or normalize data.
  • after_insert — Document saved for the first time. Send emails, create linked records.
  • validate — Runs on every save/submit before DB write. Enforce business rules with frappe.throw().
  • before_save — After validation, before DB write. Compute totals, format fields.
  • after_save — After DB write. Logging, notifications, analytics.
  • on_update — On every update after creation. Sync with external systems.
  • before_submit — Final check before document becomes permanent.
  • on_submit — Document is final. Execute real business logic (stock, accounting).
  • before_cancel — Prevent cancellation if conditions aren't met.
  • on_cancel — Reverse business actions done during submission.
  • on_trash — Before permanent deletion. Clean up related records.
  • after_delete — Record removed from DB. External cleanup.

Event Examples

python
def before_insert(doc, method):
    if not doc.source:
        doc.source = "Web"

def validate(doc, method):
    for item in doc.items:
        if item.qty <= 0:
            frappe.throw("Quantity must be greater than zero")

def before_save(doc, method):
    doc.total = sum(item.qty * item.rate for item in doc.items)

def on_submit(doc, method):
    update_loyalty_points(doc.customer)

def on_cancel(doc, method):
    restore_stock(doc.name)

Conclusion

Understanding Frappe's Document API and doc_events system is fundamental to building robust, upgrade-safe ERPNext applications. Use controller methods for per-DocType logic, and hooks for cross-app customizations that don't touch core code.