Field-Level Security
Hiding a specific custom field from the people holding a role or user group: how it is configured, what they see, why their saves do not destroy hidden values, and why required and restricted cannot be combined.
Field-level security lets you hide a named field from the people holding a particular role or user group. It applies to custom fields exactly as it does to a screen's built-in fields — a field an administrator has deliberately restricted is not readable through the custom-field API either. This is the mechanism to reach for when a value genuinely must not be seen.
Where restrictions are configured
Restrictions are set on the thing that grants access, not on the field. There are two places, and they add together.
- Per role: the restricted-fields list on a permission, in that role's permission dialog.
- Per user group: the group's own restrictions.
Field names are typed by hand, and they are keyed by the permission resource that already guards the record — employees, party-people — rather than by record type. The details below are worth reading once before you configure anything.
| Aspect | Behaviour |
|---|---|
| Two sources | A role restriction and a group restriction union together. A group can never widen what a role has restricted, and there is no override in either direction. |
| Case | Matching ignores case, so Salary, salary and SALARY are the same field. |
| Keying | Restrictions are keyed by permission resource, the same resource that guards the record itself — not by entity type and not by field group. |
| Scope of the mechanism | The same mechanism covers a screen's built-in fields and its custom fields. One restricted-fields list, one behaviour. |
| Exemption | The platform system administrator is always exempt and always sees every field. That is the same exemption the built-in mechanism already makes. |
What a restricted person sees
Nothing at all. The field is not greyed out, not blank, not marked as hidden — the entry is omitted from the record form and from the record list entirely.
An omitted field is indistinguishable from a field that was never defined. That is deliberate: showing a placeholder would tell somebody there is a value they are not allowed to see, which is itself information. It also means a colleague reporting that a field is missing may be describing a restriction rather than a fault — check the role and group restrictions before you go looking for a defect.
Saving around a hidden field
This is the part worth understanding properly, because the obvious implementation would destroy data. When somebody saves a record, the save replaces the whole set of custom-field values at once — so a field absent from the request would normally mean "clear it".
A restricted field is absent for a completely different reason: the person was never sent it. The product tells those two cases apart, and leaves a restricted field's stored value exactly as it was. Somebody who cannot see a value can no longer erase it by editing the record around it.
Writing a restricted field on purpose
An attempt to write a restricted field explicitly is refused outright, and nothing else in the same save is applied either. Rejecting the whole request rather than quietly dropping the one field is deliberate: a save reported as successful but silently missing a field is the harder failure to notice.
The refusal also fires when the submitted value happens to equal the stored one, so nobody can work out a hidden value by testing which submissions are accepted.
| Attempt | Result |
|---|---|
| Saving the record, changing only fields you can see | Succeeds. The restricted field's stored value is left exactly as it was, not cleared. |
| Sending a value for the restricted field | Refused with a message naming the field, and the record is not saved at all — not even the fields you were allowed to change. |
| Sending the restricted field's current value | Refused the same way. The result does not depend on whether your guess was right, so it cannot be used to probe the value. |
| Reading the record's custom-field values directly | The restricted field is absent from the response. This was the gap that field-level security used to leave open for custom fields specifically, and it is closed. |
Required and restricted cannot be combined
A required field can never be filled in by somebody who is not allowed to see it — they would be unable to save the record at all. The product therefore refuses the combination, in whichever order you attempt it.
| Attempt | What you see |
|---|---|
| Restricting a field that is currently required | Refused, naming the field. |
| Marking a field required while a role or group restricts it | Refused, naming the field and telling you to remove the restriction first or leave the field optional. |
Where else a restriction reaches
A restriction is not only a form-level thing. It applies consistently everywhere the field's values could otherwise surface.
- The record form: the field is omitted.
- The record list: the column is omitted.
- The custom-field value API: the field is absent from the response, and refused on write.
- The definitions spreadsheet export: restricted columns are absent from the file rather than present and blank.
A worked example
Restricting a salary field on a staff record, and confirming it behaves.
Define the field and give it a value
As an administrator who can see everything, define a custom field with the key salary on the staff record type, and set a value on one record.Restrict it on a role
Add salary to the restricted-fields list on the relevant permission in a role, then sign in as somebody holding only that role.Confirm it is absent, not blank
Open the same staff record. The Salary field should not be on the form at all, and there should be no Salary column in the staff list. If you see it empty rather than missing, the restriction is not applied.Save the record and check the value survived
As that restricted user, change something else on the record and save. Then, as the unrestricted administrator, reopen the record and confirm the salary is still there. This is the case that would destroy data in a naive implementation.Confirm the two rules that protect the configuration
Try to mark salary required while the restriction is in place — refused. Remove the restriction, mark it required, then try to restrict it again — also refused. Finally, put the same key in a user group's restrictions instead of a role's and confirm it behaves identically.