Frappe Syntax Serverscripts logo

Frappe Syntax Serverscripts

Organization
Impertio-Studio
frappe-syntax-serverscripts

Use when writing Python code for ERPNext/Frappe Server Scripts including Document Events, API endpoints, Scheduler Events, and Permission Queries. Prevents the #1 AI mistake: using import statements in Server Scripts (sandbox blocks ALL imports). Covers frappe.* methods, event name mapping, and correct v14/v15/v16 syntax. Keywords: Server Script, frappe, ERPNext, sandbox, import, doc event, validate, on_submit, before_save, server script example, import not allowed, sandbox rules, which script type to use.

Overview

PublisherImpertio-Studio
RepositoryFrappe_Claude_Skill_Package
Skill namefrappe-syntax-serverscripts
Stars
180
Forks
53
Bundled files
7
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.

  • 7 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 Serverscripts 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-serverscripts .claude/skills/frappe-syntax-serverscripts
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Frappe Syntax Serverscripts 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 Serverscripts 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 Serverscripts 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.

Frappe Server Scripts — Complete Reference

Server Scripts are Python scripts managed via Setup > Server Script in the Frappe/ERPNext UI. They run inside a RestrictedPython sandbox.

CRITICAL: The Sandbox Rule

┌──────────────────────────────────────────────────────────────────┐
│  ALL import STATEMENTS ARE BLOCKED                               │
│                                                                  │
│  import json             → ImportError: __import__ not found     │
│  from datetime import *  → ImportError: __import__ not found     │
│  import frappe           → ImportError (even frappe itself!)     │
│                                                                  │
│  EVERYTHING you need is pre-loaded in the frappe namespace.      │
│  NEVER write an import line. ALWAYS use frappe.utils.*, etc.     │
└──────────────────────────────────────────────────────────────────┘

ALWAYS use the pre-loaded namespace instead of imports:

Blocked importUse instead
import jsonfrappe.parse_json() / frappe.as_json()
from datetime import datefrappe.utils.today() / frappe.utils.now_datetime()
from frappe.utils import cintfrappe.utils.cint() (already loaded)
import requestsfrappe.make_get_request() / frappe.make_post_request()
import reNot available — restructure logic without regex
import os / import sysNot available — use a custom app instead

Enabling Server Scripts

bash
# v14: enabled by default
# v15+: DISABLED by default — you MUST enable explicitly:
bench set-config -g server_script_enabled 1
# Or set server_script_enabled: true in site_config.json

NEVER expect Server Scripts to work on Frappe Cloud shared benches — they require a private bench.

Script Types

TypeTriggerKey Variable
Document EventDocument lifecycle (save, submit, cancel)doc
APIHTTP request to /api/method/{name}frappe.form_dict
Scheduler EventCron schedule(none)
Permission QueryDocument list filteringuser, conditions

Event Name Mapping (Document Events)

CRITICAL: The UI names differ from internal hook names:

Server Script UIInternal HookFires When
Before Insertbefore_insertBefore new doc saved to DB
After Insertafter_insertAfter first DB insert
Before Validatebefore_validateBefore framework validation
Before SavevalidateBefore save (new + update)
After Saveon_updateAfter successful save
Before Submitbefore_submitBefore submit (docstatus 0→1)
After Submiton_submitAfter submit completes
Before Cancelbefore_cancelBefore cancel (docstatus 1→2)
After Cancelon_cancelAfter cancel completes
Before Deleteon_trashBefore permanent delete
After Deleteafter_deleteAfter permanent delete

NEVER confuse "Before Save" with before_save — the UI label "Before Save" maps to the validate hook. The actual before_save hook runs AFTER validate.

Decision Tree: Server Script vs Document Controller

Need custom Python logic for a DocType?
├─► Can you install a custom Frappe app?
│   ├─► YES: Use a Document Controller when you need:
│   │   • import statements (any Python library)
│   │   • File system access
│   │   • Complex class inheritance
│   │   • autoname / before_naming hooks
│   │   • Unit-testable code
│   │
│   └─► NO: Use a Server Script when:
│       • You only have UI access (no bench CLI)
│       • Logic is simple validation / field calculation
│       • You need a quick API endpoint
│       • You need dynamic permission filtering
└─► Is logic > 50 lines or needs external libraries?
    ├─► YES → Document Controller in a custom app
    └─► NO  → Server Script is fine

Quick Reference: Available in Sandbox

Pre-loaded Objects

python
doc                         # Current document (Document Event only)
frappe                      # Core namespace — ALWAYS available
frappe.db                   # Database operations
frappe.utils                # Date, number, string utilities
frappe.session              # Current session (user, csrf_token)
frappe.form_dict            # Request parameters (API scripts)
frappe.response             # Response object (API scripts)
frappe.request              # Werkzeug request object
frappe.qb                   # Query Builder (v14+)
json                        # Python json module (pre-loaded)

Core Methods

python
# Documents
frappe.get_doc(doctype, name)           # Fetch document
frappe.new_doc(doctype)                 # Create new document
frappe.get_cached_doc(doctype, name)    # Cached fetch (read-only)
frappe.get_last_doc(doctype)            # Most recent document
frappe.get_mapped_doc(...)              # Map fields between DocTypes
frappe.delete_doc(doctype, name)        # Delete document
frappe.rename_doc(doctype, old, new)    # Rename document

# Querying
frappe.get_all(doctype, filters, fields, order_by, limit)   # No permission check
frappe.get_list(doctype, filters, fields, order_by, limit)  # With permission check
frappe.db.get_value(doctype, name, fieldname)
frappe.db.get_single_value(doctype, fieldname)
frappe.db.set_value(doctype, name, fieldname, value)
frappe.db.exists(doctype, name_or_filters)
frappe.db.count(doctype, filters)
frappe.db.sql(query, values, as_dict)   # ALWAYS parameterize!
frappe.db.escape(value)                 # SQL escape
frappe.db.commit()                      # ONLY in Scheduler scripts
frappe.db.rollback()                    # ONLY in Scheduler scripts

# Messaging
frappe.throw(msg, exc, title)           # Stop execution + show error
frappe.msgprint(msg, title, indicator)  # User notification
frappe.log_error(message, title)        # Error Log entry

# HTTP (yes, these work in sandbox!)
frappe.make_get_request(url, params, headers)
frappe.make_post_request(url, data, headers)
frappe.make_put_request(url, data, headers)

# Email
frappe.sendmail(recipients, sender, subject, message)

# Utilities
frappe.utils.today()                    # "2024-01-15"
frappe.utils.now()                      # "2024-01-15 10:30:00"
frappe.utils.now_datetime()             # datetime object
frappe.utils.add_days(date, n)          # Date arithmetic
frappe.utils.add_months(date, n)
frappe.utils.date_diff(d1, d2)          # Days between dates
frappe.utils.flt(val)                   # Safe float (None → 0.0)
frappe.utils.cint(val)                  # Safe int (None → 0)
frappe.utils.cstr(val)                  # Safe string (None → "")
frappe.parse_json(string)               # JSON string → dict/list
frappe.as_json(obj)                     # dict/list → JSON string
frappe.render_template(template, ctx)   # Jinja rendering
frappe.get_url()                        # Site URL
frappe.get_hooks(hook)                  # Read app hooks
run_script(script_name, **kwargs)       # Call another Server Script

# Session / Permissions
frappe.session.user                     # Current user email
frappe.get_roles(user)                  # User's roles list
frappe.has_permission(doctype, ptype, doc)
frappe.get_fullname(user)               # User's display name
_("translatable string")               # Translation function

Python Builtins Available

python
str, int, float, bool, list, dict, tuple, set  # Types
range, enumerate, zip, map, filter              # Iteration
sum, min, max, len, sorted, reversed            # Aggregation
isinstance, type, hasattr, getattr              # Introspection
all, any, abs, round, divmod                    # Math/logic
print                                           # → server log
True, False, None                               # Constants

Python Builtins BLOCKED

python
open, file          # No file I/O
eval, exec, compile # No dynamic code execution
__import__          # No imports (this is the root cause)
globals, locals     # No scope introspection

Syntax Per Script Type

Document Event

python
# Config: Reference DocType = Sales Invoice, Event = Before Save
if doc.grand_total < 0:
    frappe.throw("Total MUST NOT be negative")

doc.requires_approval = 1 if doc.grand_total > 10000 else 0

API

python
# Config: API Method = get_customer_orders, Allow Guest = No
# Endpoint: /api/method/get_customer_orders
customer = frappe.form_dict.get("customer")
if not customer:
    frappe.throw("Parameter 'customer' is required")

orders = frappe.get_all("Sales Order",
    filters={"customer": customer, "docstatus": 1},
    fields=["name", "grand_total", "status"],
    order_by="creation desc",
    limit=20
)
frappe.response["message"] = {"orders": orders, "count": len(orders)}

Scheduler Event

python
# Config: Event Frequency = Cron, Cron Format = 0 9 * * *
overdue = frappe.get_all("Sales Invoice",
    filters={"status": "Unpaid", "due_date": ["<", frappe.utils.today()], "docstatus": 1},
    fields=["name", "customer", "grand_total"]
)
for inv in overdue:
    frappe.log_error(f"Overdue: {inv.name} ({inv.customer})", "Invoice Reminder")

frappe.db.commit()  # ALWAYS commit in Scheduler scripts

Permission Query

python
# Config: Reference DocType = Sales Invoice
# Variables available: user, conditions
roles = frappe.get_roles(user)
if "System Manager" in roles:
    conditions = ""
elif "Sales User" in roles:
    conditions = f"`tabSales Invoice`.owner = {frappe.db.escape(user)}"
else:
    conditions = "1=0"

Version Differences

Featurev14v15v16
Server Scripts enabledBy defaultDisabled by defaultDisabled by default
Enable commandNot neededbench set-config -g server_script_enabled 1Same as v15
frappe.qb (Query Builder)AvailableAvailableAvailable
run_script() for librariesv13+AvailableAvailable
frappe.make_get_request()AvailableAvailableAvailable
Frappe Cloud shared benchSupportedNOT supportedNOT supported

Top 5 Rules

  1. NEVER write import — everything is in the frappe namespace
  2. NEVER call doc.save() inside a Before Save script — causes infinite loop
  3. NEVER call frappe.db.commit() in Document Event scripts — framework handles it
  4. ALWAYS call frappe.db.commit() at the end of Scheduler scripts
  5. ALWAYS use parameterized queries: %(var)s with dict, NEVER f-strings in SQL

References

Cross-References

  • frappe-syntax-api — Frappe REST API and whitelisted methods
  • frappe-syntax-doctype — DocType field types and schema
  • frappe-core-database — frappe.db deep dive
  • frappe-core-permissions — Permission system architecture
  • frappe-errors-common — Error handling patterns

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 Serverscripts AI skill do?

Use when writing Python code for ERPNext/Frappe Server Scripts including Document Events, API endpoints, Scheduler Events, and Permission Queries. Prevents the #1 AI mistake: using import statements in Server Scripts (sandbox blocks ALL imports). Covers frappe.* methods, event name mapping, and correct v14/v15/v16 syntax. Keywords: Server Script, frappe, ERPNext, sandbox, import, doc event, validate, on_submit, before_save, server script example, import not allowed, sandbox rules, which script type to use.

Why use Frappe Syntax Serverscripts on TypingMind?

Because you install it once and use it with any model. Frappe Syntax Serverscripts 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 Serverscripts 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-serverscripts. 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 Serverscripts?

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 Serverscripts?

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

Is the Frappe Syntax Serverscripts 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 👇