---
title: "Document Lifecycle"
space: "Frappe Framework"
url: "https://www.prilk.com/docs/frappe-framework/document-lifecycle"
updated: "2026-05-14"
---

## Document Lifecycle

Every Frappe document goes through a defined lifecycle. Hooks fire at each stage — apps can validate, react, or veto.

### Stages

| Stage | When | Common use |
|---|---|---|
| `before_insert` | Just before first save (record doesn't exist yet) | Compute defaults from context |
| `after_insert` | After first save | Create related records |
| `validate` | Before every save (insert OR update) | Business rule checks |
| `before_save` | Before every save | Final adjustments |
| `on_update` | After every save | Sync to external systems |
| `before_submit` | Before submit | Final checks before locking |
| `on_submit` | After submit | Create GL Entries, trigger workflow |
| `before_cancel` | Before cancel | Verify cancellation allowed |
| `on_cancel` | After cancel | Reverse GL Entries |
| `on_trash` | Before delete | Cleanup |

### Submittable documents
Documents like Sales Invoice, Stock Entry, Journal Entry are **submittable**:

| docstatus | Meaning | Editable? |
|---|---|---|
| 0 | Draft | Yes |
| 1 | Submitted (finalized; ledger entries created) | No — Cancel + Amend |
| 2 | Cancelled (reversed) | No |

You don't delete submitted documents — you Cancel and (optionally) Amend.

### Hook resolution
For each lifecycle stage, Frappe runs:

1. The DocType's controller method (e.g., `before_save(self)`)
2. App `doc_events` hooks (in install order)

Multiple apps can hook the same event. Errors from any hook abort the save — partial state never persists.

### Idempotency
Don't assume hooks fire only once per "user action". A retry, a queued job, or a script can trigger the same event multiple times. Design hooks to be idempotent — re-running them shouldn't double-charge or double-create.
