In many Frappe/ERPNext implementations, developers rely on the “Read Only” field property expecting it to guarantee that the value cannot be modified by the user. However, this assumption is not accurate. The current Read Only behavior only affects the Form UI and can be completely bypassed from the client side.
This post aims to open a discussion on adding a new field-level property that enforces server-controlled write access, ensuring that field values cannot be changed by the client under any circumstance.
The Problem
1. Misconception About “Read Only”
Many developers assume that setting a field as Read Only makes it immutable.
But in the Frappe Framework, Read Only only prevents visual editing in the form it does not protect the field from programmatic modification on the client.
2. Client Can Easily Modify Values
Even if a field is Read Only, the client can still modify it using:
- Browser Console
- JavaScript injections
- Client scripts
Example using Console:
cur_frm.set_value(“read_only_field”, “new value”)
The server accepts this change because Frappe currently trusts incoming field values unless additional validation is implemented.
3. Real Example: Employee Advance DocType
A clear real-world example is the Employee Advance DocType.
It contains several fields that are meant to be computed only by server-side logic, such as:
- Paid Amount – updated only when a Payment Entry is submitted
- Returned Amount – updated only when a Journal Entry is submitted
- Claimed Amount – updated only when an Expense Claim is submitted
These fields are marked as Read Only because they should never be manually edited by the user.
However, due to the current behavior of Read Only:
The client can still modify these values through the browser console before submission.
The server accepts the modified values since it does not block client-side writes.
This means a user can submit an Employee Advance with incorrect financial values, for example, a manually set Paid Amount even though no Payment Entry exists.
This results in:
- incorrect financial data
- inconsistent reporting
- behavior that contradicts the intended server-controlled logic
4. Existing Workarounds Are Not Practical
Developers currently rely on workarounds such as:
- resetting values manually inside validate()
- custom hooks to enforce restrictions
- using db_set() (which bypasses validation entirely)
- Change the field level (permlevel)
5. Limitations of Using Field Permission Level (permlevel) as a Workaround
Some developers attempt to restrict client-side modifications by increasing a field’s Permission Level (permlevel). However, this approach introduces behavior that is unexpected and inconsistent, and it does not provide a reliable mechanism for “server-only write”.
a. Server Can Modify the Field Only Inside Document Hooks
When a field is assigned a high permlevel (e.g., permlevel = 1) and the current user has only Read permission for that level:
- Directly updating the field from a standard save operation does not work:
doc.field_with_high_permlevel = "new value"
doc.save() # Frappe will reset the value to the database version
This happens because Frappe first clears the incoming client values for fields the user cannot write to, and then reloads the original database values before running the save process.
b. Updating the Field Inside Hooks Works Correctly
If the modification happens inside one of the server-side document hooks such as:
-
validate() -
before_save() -
on_submit()
then the update will succeed:
def validate(self):
self.field_with_high_permlevel = "new value"
c. Why This Is a Problem
This inconsistent behavior produces several issues:
-
server-side updates only work inside hooks, not outside normal code paths
-
Background jobs, integration scripts, and API-based updates often need to update the field outside hooks
-
Developers cannot use
doc.save()reliably to update controlled fields -
Logic becomes fragmented because some updates must be forced into hooks unnecessarily
Summary
Using permlevel as a workaround to block client-side writes introduces several practical and technical limitations:
-
It behaves inconsistently (server updates work only inside hooks, but fail when using
doc.save()outside them). -
It complicates server-side logic and forces developers to restructure updates unnaturally just to bypass permission restrictions.
-
It requires granting unnecessary Read permissions for users just to make the field visible.
It becomes increasingly unmaintainable when:
- The DocType contains many sensitive or computed fields,
- Multiple developers contribute to the same module,
- or long-term maintenance and reliability are required.
Proposed Solution: Add a New Field Property
These limitations highlight the need for a dedicated field property named for example, Ignore Client Write, Server Only Write or Disallow Client Write
which would:
-
block all client-side attempts to modify the field (UI, JS, Console, etc.),
-
allow unrestricted server-side updates from any context (hooks, controllers, background jobs, API scripts, or whitelisted methods),
-
and eliminate the need to adjust permission levels or implement custom validation workarounds.
A framework-level solution would provide a clean, predictable, and scalable way to ensure “server-only write” behavior across all DocTypes.
