Frappe Syntax Hooks Events logo

Frappe Syntax Hooks Events

Organization
Impertio-Studio
frappe-syntax-hooks-events

Use when implementing document lifecycle hooks via doc_events in hooks.py, understanding event execution order, or extending/overriding document behavior from another app. Prevents silent hook failures from wrong event names, incorrect execution order assumptions, and broken override chains. Covers doc_events hook syntax, all document events (before_insert, validate, on_submit, etc.), event execution order, extend vs override behavior, cross-app doc_events. Keywords: doc_events, hooks.py, before_insert, validate, on_submit, on_cancel, lifecycle, document events, override, extend, event order, which event fires when, before_save vs validate, document event list..

Overview

PublisherImpertio-Studio
RepositoryFrappe_Claude_Skill_Package
Skill namefrappe-syntax-hooks-events
Stars
180
Forks
53
Bundled files
3
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 3 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by Impertio-Studio on GitHub. Read the source before you install it.

Installation

Install the Frappe Syntax Hooks Events AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package.git /tmp/Frappe_Claude_Skill_Package
mkdir -p .claude/skills
cp -r /tmp/Frappe_Claude_Skill_Package/skills/source/syntax/frappe-syntax-hooks-events .claude/skills/frappe-syntax-hooks-events
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Frappe Syntax Hooks Events in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Frappe Syntax Hooks Events on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Frappe Syntax Hooks Events is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Document Lifecycle Hooks (doc_events)

Quick Reference: Event Execution Order

Insert (new document)

OrderEventPurposeCan Raise?
1before_insertSet defaults before namingYES
2before_namingModify naming logicYES
3autonameSet the name propertyYES
4before_validateAuto-set missing valuesYES
5validateValidation logic — throw to abortYES
6before_saveFinal mutations before DB writeYES
7db_insertInternal — writes row to DB
8after_insertPost-insert logic (runs once ever)YES
9on_updatePost-save logic (runs on every save)YES
10on_changeFires if any field value changedYES

Save (existing document)

OrderEventPurpose
1before_validateAuto-set missing values
2validateValidation logic — throw to abort
3before_saveFinal mutations before DB write
4db_updateInternal — updates row in DB
5on_updatePost-save logic
6on_changeFires if any field value changed

Submit

OrderEventPurpose
1before_validateAuto-set missing values
2validateValidation logic
3before_saveFinal mutations before DB write
4before_submitPre-submit logic — throw to abort
5db_updateInternal — updates row in DB
6on_submitPost-submit logic (GL entries etc)
7on_updatePost-save logic
8on_changeFires if any field value changed

Cancel

OrderEventPurpose
1before_cancelPre-cancel validation
2db_updateInternal — updates row in DB
3on_cancelPost-cancel logic (reverse GL etc)
4on_changeFires if any field value changed

Delete

OrderEventPurpose
1on_trashPre-delete cleanup
2after_deletePost-delete logic

Other Operations

OperationEvents (in order)
Renamebefore_renameafter_rename
Amendbefore_insert chain runs on the new amended doc
Update After Submitbefore_update_after_submitdb_updateon_update_after_submiton_change

doc_events in hooks.py: Syntax

Basic Structure

python
# hooks.py
doc_events = {
    "Sales Invoice": {
        "on_submit": "myapp.events.sales_invoice.on_submit",
        "on_cancel": "myapp.events.sales_invoice.on_cancel",
    },
    "Purchase Order": {
        "validate": "myapp.events.purchase_order.validate",
    }
}

Wildcard: Apply to ALL DocTypes

python
doc_events = {
    "*": {
        "after_insert": "myapp.events.global_handler.after_insert_all",
        "on_update": "myapp.events.global_handler.track_changes",
    }
}

ALWAYS use "*" (string with asterisk) as the key. This fires the handler for every DocType.

Multiple Handlers per Event

python
doc_events = {
    "Sales Invoice": {
        "on_submit": [
            "myapp.events.accounting.create_gl_entries",
            "myapp.events.notifications.send_invoice_email",
        ]
    }
}

Handler Function Signature

python
# myapp/events/sales_invoice.py
def on_submit(doc, method=None):
    """
    doc    — the Document instance (e.g., Sales Invoice)
    method — string name of the event (e.g., "on_submit"), or None
    """
    if doc.grand_total > 10000:
        frappe.sendmail(...)

ALWAYS accept method as the second parameter (with default None). Frappe passes it automatically.


Decision Tree: Which Event to Use

"I need to validate data before saving"

→ Use validate. ALWAYS raise frappe.throw() here to block invalid saves.

"I need to set default values automatically"

→ Use before_validate. This runs before validate, so your defaults are set before validation checks.

"I need to run logic only on first creation"

→ Use after_insert. This fires ONLY on insert, NEVER on subsequent saves.

"I need to run logic on every save (insert + update)"

→ Use on_update. This fires on both insert and save operations.

"I need to create linked documents after submit"

→ Use on_submit. NEVER create linked docs in validate — the document is not yet committed.

"I need to reverse linked documents on cancel"

→ Use on_cancel. ALWAYS clean up GL entries, stock ledger entries, and linked docs here.

"I need to modify the document name"

→ Use autoname in the controller, or before_naming for conditional logic.

"I need to prevent deletion under certain conditions"

→ Use on_trash. Raise frappe.throw() to block deletion.

"I need to update a submitted document's fields"

→ Use before_update_after_submit for validation and on_update_after_submit for side effects.

"I need logic that runs only when values actually changed"

→ Use on_change. This fires only when at least one field value differs from the DB state.


doc_events vs Controller Events

Both mechanisms trigger the SAME events. The difference is WHERE you register them.

AspectController (class method)doc_events (hooks.py)
Location{doctype}.py controller filehooks.py in your app
Use whenYou OWN the DocTypeYou are EXTENDING another app's DocType
ExecutionRuns first (controller)Runs after controller method
Multiple appsOnly one controller per DocTypeMultiple apps can register handlers

ALWAYS use doc_events when hooking into a DocType you do NOT own. NEVER modify another app's controller file directly.

Execution Order Within a Single Event

For a given event (e.g., validate):

  1. Controller method runs first (def validate(self))
  2. doc_events handlers run in app installation order
  3. Wildcard "*" handlers run after specific DocType handlers

extend_doctype_class [v16+]

In Frappe v16+, extend_doctype_class provides a cleaner alternative to doc_events for adding methods to existing DocTypes.

hooks.py

python
extend_doctype_class = {
    "Sales Invoice": [
        "myapp.overrides.sales_invoice.SalesInvoiceExtension"
    ]
}

Extension Class (Mixin)

python
# myapp/overrides/sales_invoice.py
import frappe

class SalesInvoiceExtension:
    def validate(self):
        """This is called as part of the controller chain."""
        if self.grand_total < 0:
            frappe.throw("Grand total cannot be negative")

    def custom_method(self):
        """Custom methods are also available on the doc instance."""
        return self.items

Key Rules

  • ALWAYS use extend_doctype_class over override_doctype_class in v16+ when multiple apps may extend the same DocType.
  • Multiple apps can extend the same DocType — extensions stack via MRO.
  • Class resolution order follows hooks priority: class Final(App2Mixin, App1Mixin, Original).
  • Extension methods (like validate) run as part of the controller, NOT as separate doc_events handlers.

override_doctype_class [v14+]

Completely replaces the controller class. Use with extreme caution.

python
# hooks.py
override_doctype_class = {
    "ToDo": "myapp.overrides.todo.CustomToDo"
}
python
# myapp/overrides/todo.py
from frappe.desk.doctype.todo.todo import ToDo

class CustomToDo(ToDo):
    def validate(self):
        super().validate()  # ALWAYS call super() to preserve original logic
        # Your additions here

NEVER use override_doctype_class if extend_doctype_class is available (v16+). Only ONE app can override a DocType — last-installed app wins, silently breaking other apps.


Multi-App Event Ordering

When multiple apps register doc_events for the same DocType and event:

  1. Handlers execute in app installation order (as listed in sites/{site}/site_config.jsoninstalled_apps).
  2. The order can be changed via Setup > Installed Applications > Update Hooks Resolution Order.
  3. For override_doctype_class, the last-installed app wins (only one override applies).
  4. For extend_doctype_class (v16+), all extensions stack cumulatively.

Transaction Behavior

All document events from before_validate through on_change run inside a single database transaction.

  • If ANY event raises an exception, the ENTIRE operation rolls back (including db_insert/db_update).
  • after_insert, on_update, on_submit, on_cancel — all run BEFORE the transaction commits.
  • The transaction commits only AFTER all events complete successfully.
  • after_delete runs after the DELETE statement but still within the request transaction.

NEVER assume data is committed to DB inside any event handler. Other concurrent requests will NOT see your changes until the full request completes.


Critical Rules

  1. ALWAYS use frappe.throw() to abort operations — NEVER use raise Exception.
  2. NEVER modify doc.name outside of autoname or before_naming.
  3. ALWAYS call super().{event}() when overriding controller methods in subclasses.
  4. NEVER use doc.save() inside validate or before_save — this causes infinite recursion.
  5. ALWAYS use doc.flags.ignore_permissions = True explicitly if your hook needs to bypass permissions — NEVER assume hooks run as Administrator.
  6. NEVER put slow operations (API calls, file I/O) in validate — use after_insert or on_update with frappe.enqueue() instead.
  7. ALWAYS use doc.flags to communicate between events in the same request (e.g., doc.flags.skip_notification = True).
  8. NEVER rely on on_change for critical logic — it only fires when values actually differ from the database state.

See Also

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Frappe Syntax Hooks Events AI skill do?

Use when implementing document lifecycle hooks via doc_events in hooks.py, understanding event execution order, or extending/overriding document behavior from another app. Prevents silent hook failures from wrong event names, incorrect execution order assumptions, and broken override chains. Covers doc_events hook syntax, all document events (before_insert, validate, on_submit, etc.), event execution order, extend vs override behavior, cross-app doc_events. Keywords: doc_events, hooks.py, before_insert, validate, on_submit, on_cancel, lifecycle, document events, override, extend, event orde...

Why use Frappe Syntax Hooks Events on TypingMind?

Because you install it once and use it with any model. Frappe Syntax Hooks Events is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Frappe Syntax Hooks Events in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package/tree/main/skills/source/syntax/frappe-syntax-hooks-events. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Frappe Syntax Hooks Events?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Frappe Syntax Hooks Events?

As many as you like. As long as a model supports skills, you can use Frappe Syntax Hooks Events with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Frappe Syntax Hooks Events AI skill free?

Yes. It is published on GitHub by Impertio-Studio under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇