diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py
index 0d785fc853..21fb77dfdd 100644
--- a/frappe/automation/doctype/assignment_rule/assignment_rule.py
+++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py
@@ -200,6 +200,9 @@ def get_assignments(doc) -> list[dict]:
@frappe.whitelist()
def bulk_apply(doctype: str, docnames: str | list[str]):
+ if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"):
+ frappe.throw(_("You are not allowed to perform bulk actions"), frappe.PermissionError)
+
docnames = frappe.parse_json(docnames)
background = len(docnames) > 5
diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json
index 8dcb413e78..bdc97b4993 100644
--- a/frappe/core/doctype/user/user.json
+++ b/frappe/core/doctype/user/user.json
@@ -53,8 +53,8 @@
"search_bar",
"notifications",
"list_settings_section",
- "list_sidebar",
"bulk_actions",
+ "list_sidebar",
"view_switcher",
"form_settings_section",
"form_sidebar",
@@ -598,7 +598,7 @@
"unique": 1
},
{
- "description": "\n Click here to learn about token-based authentication\n",
+ "description": "\n Click here to learn about token-based authentication\n",
"fieldname": "generate_keys",
"fieldtype": "Button",
"label": "Generate Keys",
@@ -771,13 +771,13 @@
"default": "1",
"fieldname": "search_bar",
"fieldtype": "Check",
- "label": "Search Bar"
+ "label": "Show search bar"
},
{
"default": "1",
"fieldname": "notifications",
"fieldtype": "Check",
- "label": "Notifications"
+ "label": "Allow notifications"
},
{
"collapsible": 1,
@@ -789,19 +789,20 @@
"default": "1",
"fieldname": "list_sidebar",
"fieldtype": "Check",
- "label": "Sidebar"
+ "label": "Show sidebar"
},
{
"default": "1",
"fieldname": "bulk_actions",
"fieldtype": "Check",
- "label": "Bulk Actions"
+ "label": "Allow bulk actions",
+ "permlevel": 1
},
{
"default": "1",
"fieldname": "view_switcher",
"fieldtype": "Check",
- "label": "View Switcher"
+ "label": "Show view switcher"
},
{
"collapsible": 1,
@@ -813,25 +814,25 @@
"default": "1",
"fieldname": "form_sidebar",
"fieldtype": "Check",
- "label": "Sidebar"
+ "label": "Show sidebar"
},
{
"default": "1",
"fieldname": "timeline",
"fieldtype": "Check",
- "label": "Timeline"
+ "label": "Show timeline"
},
{
"default": "1",
"fieldname": "dashboard",
"fieldtype": "Check",
- "label": "Dashboard"
+ "label": "Show dashboard"
},
{
"default": "0",
"fieldname": "show_absolute_datetime_in_timeline",
"fieldtype": "Check",
- "label": "Show Absolute Datetime in Timeline"
+ "label": "Show absolute datetime in timeline"
},
{
"fieldname": "sessions_tab",
@@ -850,7 +851,7 @@
"default": "0",
"fieldname": "form_navigation_buttons",
"fieldtype": "Check",
- "label": "Navigation Buttons"
+ "label": "Show navigation buttons"
}
],
"icon": "fa fa-user",
@@ -904,7 +905,7 @@
}
],
"make_attachments_public": 1,
- "modified": "2026-01-12 16:04:21.542524",
+ "modified": "2026-02-22 13:44:36.317890",
"modified_by": "Administrator",
"module": "Core",
"name": "User",
diff --git a/frappe/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py
index 28b4415ddf..75d9f8690d 100644
--- a/frappe/desk/doctype/bulk_update/bulk_update.py
+++ b/frappe/desk/doctype/bulk_update/bulk_update.py
@@ -54,7 +54,10 @@ def submit_cancel_or_update_docs(
action: str = "submit",
data: str | dict[str, Any] | None = None,
task_id: str | None = None,
-):
+) -> list[str] | None:
+ if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"):
+ frappe.throw(_("You are not allowed to perform bulk actions."), frappe.PermissionError)
+
if isinstance(docnames, str):
docnames = frappe.parse_json(docnames)
diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py
index 04f1053ead..da874a06e7 100644
--- a/frappe/desk/doctype/tag/tag.py
+++ b/frappe/desk/doctype/tag/tag.py
@@ -2,6 +2,7 @@
# License: MIT. See LICENSE
import frappe
+from frappe import _
from frappe.model.document import Document
from frappe.query_builder import DocType
from frappe.utils import unique
@@ -43,6 +44,10 @@ def add_tag(tag: str, dt: str, dn: str, color: str | None = None):
@frappe.whitelist()
def add_tags(tags: str | list[str], dt: str, docs: str | list[str], color: str | None = None):
"adds a new tag to a record, and creates the Tag master"
+
+ if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"):
+ frappe.throw(_("You are not allowed to perform bulk actions"), frappe.PermissionError)
+
tags = frappe.parse_json(tags)
docs = frappe.parse_json(docs)
for doc in docs:
diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py
index 2af0a786a3..c84dc29784 100644
--- a/frappe/desk/form/assign_to.py
+++ b/frappe/desk/form/assign_to.py
@@ -141,9 +141,11 @@ def add(args: dict[str, Any] | None = None, *, ignore_permissions: bool | int =
@frappe.whitelist()
-def add_multiple(args: dict[str, Any] | None = None):
- if not args:
- args = frappe.local.form_dict
+def add_multiple() -> None:
+ if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"):
+ frappe.throw(_("You are not allowed to perform bulk actions"), frappe.PermissionError)
+
+ args = frappe.local.form_dict
docname_list = json.loads(args["name"])
@@ -181,6 +183,9 @@ def remove(doctype: str, name: str | int, assign_to: str, ignore_permissions: bo
@frappe.whitelist()
def remove_multiple(doctype: str, names: str, ignore_permissions: bool | int = False):
+ if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"):
+ frappe.throw(_("You are not allowed to perform bulk actions"), frappe.PermissionError)
+
docname_list = json.loads(names)
for name in docname_list:
diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py
index c9c42cd2f1..14cc6a8756 100644
--- a/frappe/desk/reportview.py
+++ b/frappe/desk/reportview.py
@@ -597,6 +597,9 @@ def parse_field(field: str) -> tuple[str | None, str]:
@frappe.whitelist(methods=["POST", "DELETE"])
def delete_items():
"""delete selected items"""
+ if not (frappe.get_cached_value("User", frappe.session.user, "bulk_actions")):
+ frappe.throw(_("You are not allowed to perform bulk actions."), frappe.PermissionError)
+
import json
items = sorted(json.loads(frappe.form_dict.get("items")), reverse=True)
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 4fcd01514b..d9c6514556 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -2533,6 +2533,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
// Copy to clipboard
actions_menu_items.push(copy_to_clipboard());
+ if (!frappe.boot.desk_settings?.bulk_actions) return actions_menu_items;
+
// bulk edit
if (has_editable_fields(doctype) && is_bulk_edit_allowed(doctype)) {
actions_menu_items.push(bulk_edit());
diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py
index 39b0dc4e47..3a3770bce1 100644
--- a/frappe/utils/print_format.py
+++ b/frappe/utils/print_format.py
@@ -36,6 +36,9 @@ def download_multi_pdf(
"""
Calls _download_multi_pdf with the given parameters and returns the response
"""
+ if not (frappe.get_cached_value("User", frappe.session.user, "bulk_actions")):
+ frappe.throw(_("You are not allowed to perform bulk actions."), frappe.PermissionError)
+
return _download_multi_pdf(doctype, name, format, no_letterhead, letterhead, options)
@@ -51,6 +54,9 @@ def download_multi_pdf_async(
"""
Calls _download_multi_pdf with the given parameters in a background job, returns task ID
"""
+ if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"):
+ frappe.throw(_("You are not allowed to perform bulk actions"), frappe.PermissionError)
+
task_id = str(uuid.uuid4())
if isinstance(doctype, dict):
doc_count = sum([len(doctype[dt]) for dt in doctype])