Frappe Framework v15 Document API
A complete, technically accurate guide for ERPNext developers, app builders, and integrators.
Introduction: What is the Document API in Frappe v15?
The Document API is the core interface for creating, reading, updating, deleting, submitting, canceling, and manipulating database records in Frappe. Every record in the framework—such as ToDo, Sales Invoice, User, Item—is represented as a DocType, and an instance of that record is represented as a Document object.
The Document API handles:
- ORM-based database interactions
- Permissions
- Child tables
- Workflows
- Events (validate, before_save, on_submit, etc.)
- Controller methods
This page explains how to use the full Frappe v15 Document API with precise syntax and examples.
How to Create a New Document in Frappe v15
Using frappe.new_doc()
doc = frappe.new_doc("ToDo")
doc.description = "Prepare documentation"
doc.date = "2025-03-10"
doc.insert()
- Initializes a new document object
- Does NOT save it until .insert() is called
- Permissions are applied automatically
Using frappe.get_doc() with values
doc = frappe.get_doc({
"doctype": "ToDo",
"description": "Prepare docs",
"date": "2025-03-10"
})
doc.insert()
This is primarily used for building documents dynamically (e.g., API endpoints).
How to Load an Existing Document
Load by DocType and Name
doc = frappe.get_doc("ToDo", "TD-0001")
Load using dictionary format
(when receiving REST API payloads)
doc = frappe.get_doc(json_payload)
How to Modify and Save a Document
Update properties and save
doc = frappe.get_doc("ToDo", "TD-0001")
doc.description = "Updated description"
doc.save()
.save() triggers all standard controller events:
- before_save
- validate
- on_update (if exists)
How to Submit, Cancel, and Amend Documents
Frappe uses a 3-stage lifecycle for documents that use Submit Workflow Logic (e.g., Sales Invoice, Purchase Order, Stock Entry).
Submit a document
doc = frappe.get_doc("Sales Invoice", "SINV-0001")
doc.submit()
Triggers:
- before_submit
- on_submit
Cancel a document
doc = frappe.get_doc("Sales Invoice", "SINV-0001")
doc.cancel()
Triggers:
- before_cancel
- on_cancel
Amend a document
doc = frappe.get_doc("Sales Invoice", "SINV-0001")
new_doc = doc.amend() # returns new draft document
Creates a new draft with reference to the old document.
Updating Documents Using db_set
Use db_set() when you want to override values directly in the database, bypassing validation events.
doc.db_set("status", "Closed")
This:
- Updates in database immediately
- Does NOT trigger validation
- Optionally notifies via realtime
Useful for background jobs and system processes.
Working with Child Tables
Child tables are stored as dependent documents with parent, parenttype, and parentfield fields.
Append a child row
doc = frappe.get_doc("Sales Invoice", "SINV-0001")
doc.append("items", {
"item_code": "ITEM-001",
"qty": 5,
"rate": 100
})
doc.save()
Loop through child table
for row in doc.items:
print(row.item_code, row.qty)
Document Methods & Controller API
Frappe allows adding custom business logic by defining methods inside the DocType’s Python controller.
Calling custom methods
doc = frappe.get_doc("ToDo", "TD-0001")
doc.run_reminder() # custom method inside ToDo.py
Methods receive the document as self.
Trigger validation manually
doc.validate()
However, Frappe automatically handles validation on save/submit.
Document Events (Server-Side Lifecycle Hooks)
Each controller can implement event functions such as:
- validate
- before_insert
- after_insert
- before_save
- on_update
- before_submit
- on_submit
- before_cancel
- on_cancel
- on_trash
- after_delete
Events execute automatically based on actions performed via the Document API.
Accessing Metadata and Fields
List all fields
fields = doc.meta.fields
Get a specific field value
value = doc.get("description")
Set a field value
doc.set("description", "New value")
.set() does not bypass validation.
Permission Handling
Frappe validates permissions automatically when using:
- frappe.get_doc()
- .save()
- .submit()
- .cancel()
To bypass permission checks (system use only):
frappe.get_doc("ToDo", "TD-0001", for_update=True)
Or disable permission checks (use cautiously):
doc.flags.ignore_permissions = True
doc.save()
Deleting Documents
Soft delete (standard)
doc = frappe.get_doc("ToDo", "TD-0001")
doc.delete()
Triggers:
before_trash → deletes → after_delete
Hard delete (not recommended)
frappe.delete_doc("ToDo", "TD-0001", force=True)
Use only for system purges.
Advanced Usage & Integration Patterns
1. Using Document API in REST Endpoints
@frappe.whitelist()
def create_todo():
data = frappe.form_dict
doc = frappe.get_doc({
"doctype": "ToDo",
**data
})
doc.insert()
return doc
2. Using Document API inside Background Jobs
def process_invoice(inv):
doc = frappe.get_doc("Sales Invoice", inv)
doc.status = "Processing"
doc.save()
Recommended: add enqueue_after_commit=True when queueing jobs.
3. Creating Documents from Workflow Transitions
Used in ERPNext for Sales Cycle, Manufacturing, HR, etc.
new_doc = frappe.new_doc("Delivery Note")
new_doc.update_from_doc(source_doc, table_maps)
new_doc.insert()
Troubleshooting Common Issues
Validation Error on Save
Ensure mandatory fields are filled. Use doc.flags.ignore_validate = True only for system tasks.
PermissionError on Load
User lacks permission. Check Role Permission Manager.
Child Table Not Saved
Always call .save() after append().
Document Not Found
Check if the record is deleted or naming series is misconfigured.
Cross-References
- Realtime API
- Background Jobs
- Permission API
- Workflow API
- REST API
Each interacts heavily with the Document API.
Conclusion
The Frappe v15 Document API is the backbone of ERPNext and all Frappe applications. It provides a rich, Pythonic ORM interface with built-in validation, event triggers, permissions, child table management, and workflow support. Mastering this API is essential for building robust ERP apps, automations, and custom business logic on the Frappe Framework.