# Server Script (ERPNext glossary)

Canonical: https://erpfly.com/glossary/server-script/
Last updated: September 14, 2026

**Definition:** A Server Script is a Frappe record holding Python code that is written in the desk and run in a restricted sandbox. Depending on its type, it runs on a document event, answers an API call, runs on a schedule or filters list queries, all without a custom app or a deploy.

Also called: Server Scripts, Frappe server script, ERPNext server script

Open the Server Script list in ERPNext, click New, and you get a code editor and a Script Type dropdown. Whatever you type there is stored in the database and executed by Frappe through `safe_exec`, which uses RestrictedPython to limit what the code can reach.

### The four types (five on v16)

**DocType Event** attaches to one DocType and one lifecycle event. The list covers Before Validate, Before Save and After Save, Before and After versions of Insert, Submit, Cancel, Delete and Rename, the two "Submitted Document" save events for fields allowed on submit, and Before Print. You get the document as `doc`. The labels don't match controller method names one to one (Before Save runs at `validate`), which matters when you move a script into code later.

**API** turns the script into an endpoint under `/api/method/<method name>`. You can tick Allow Guest for unauthenticated calls and Enable Rate Limit to throttle them, and you return data by setting `frappe.response["message"]`.

**Scheduler Event** runs on a frequency: Hourly, Daily, Weekly, Monthly, Yearly, the "Long" queue variants, or a Cron expression.

**Permission Query** adds a condition to list queries for a DocType. Frappe hands you `user` and reads back whatever you assign to `conditions`.

Frappe v16 adds a fifth type, Workflow Task.

### Turning them on

On a self-hosted bench, Server Scripts are off until you set `server_script_enabled`. In the v15 and v16 source, Frappe reads that key only from `common_site_config.json`, so set it bench-wide:

```bash
bench set-config -g server_script_enabled 1
```

If it's missing, running a script fails with a "Server Scripts Disabled" error. On Frappe Cloud, the docs say you need a private bench; shared public benches don't allow them.

### An example: sales reps see their own orders

```python
# Script Type: Permission Query
# Reference Document Type: Sales Order
is_manager = frappe.db.exists(
    "Has Role", {"parent": user, "parenttype": "User", "role": "Sales Manager"}
)
if user != "Administrator" and not is_manager:
    conditions = "`tabSales Order`.owner = " + frappe.db.escape(user)
```

Keep in mind what this does and doesn't cover. Permission query conditions filter results from `frappe.db.get_list`, which drives list views and reports. They don't decide whether someone can open a single document by its URL. That's the job of role permissions, or a `has_permission` hook in an app's hooks.py.

### Limits to know before you rely on one

The sandbox has no `import`, and `frappe.db.sql` accepts read queries only. Names starting with an underscore are blocked. DocType Event scripts are also skipped while `bench migrate` or an app install is running, so a rule enforced by a script won't fire on records a patch creates.

There's no test runner and no Git history either. For a two-line validation that's a fair trade. For anything posting to the ledger, it isn't, and we've set out where we draw that line in [Server Scripts vs a custom app](https://erpfly.com/blog/server-scripts-vs-custom-app-erpnext/). erpfly doesn't generate Server Scripts for business rules. It writes the same logic as Python in a Frappe app, wired through `doc_events`, so it ships with tests in the pull request.

See also: https://erpfly.com/glossary/hooks-py/, https://erpfly.com/glossary/frappe-app/, https://erpfly.com/glossary/bench/, https://erpfly.com/glossary/doctype/

### Sources

- [Server Script, Frappe Framework documentation](https://docs.frappe.io/framework/user/en/desk/scripting/server-script)
- [server_script.json (version-15), frappe/frappe on GitHub](https://github.com/frappe/frappe/blob/version-15/frappe/core/doctype/server_script/server_script.json)
- [frappe/utils/safe_exec.py (version-15), frappe/frappe on GitHub](https://github.com/frappe/frappe/blob/version-15/frappe/utils/safe_exec.py)
- [server_script_utils.py (version-15), frappe/frappe on GitHub](https://github.com/frappe/frappe/blob/version-15/frappe/core/doctype/server_script/server_script_utils.py)