---
title: "Payload CMS — Field-Level Access Control"
aliases: [payload-field-access, payload-field-permissions]
tags: [payload-cms, access-control, permissions, cms, nextjs]
sources: [raw/access-control__fields.md]
created: 2026-05-15
updated: 2026-05-15
---

# Payload CMS — Field-Level Access Control

Field-level access control restricts who can **create**, **read**, or **update** individual fields within a document. Defined directly in the [[wiki/tech-patterns/payload-cms-installation|Field Config]] via the `access` property.

## Key Takeaways

- Field access is set per-field in the `access` object with `create`, `read`, `update` functions.
- Each function returns a **boolean** — no Query constraints supported (unlike [[wiki/tech-patterns/payload-cms-collection-access-control|Collection Access Control]]).
- `create: false` → value silently discarded (not an error).
- `read: false` → field property **omitted** entirely from the API response.
- `update: false` → value silently discarded, field unchanged (not an error).

## Config Shape

```ts
import type { Field } from 'payload'

export const SecretField: Field = {
  name: 'internalNote',
  type: 'text',
  access: {
    create: ({ req: { user } }) => user?.role === 'admin',
    read:   ({ req: { user } }) => user?.role === 'admin',
    update: ({ req: { user } }) => user?.role === 'admin',
  },
}
```

Full collection example:

```ts
import type { CollectionConfig } from 'payload'

export const Posts: CollectionConfig = {
  slug: 'posts',
  fields: [
    {
      name: 'title',
      type: 'text',
      access: {
        create: ({ req: { user } }) => Boolean(user),
        read:   () => true,
        update: ({ req: { user } }) => user?.role === 'editor',
      },
    },
  ],
}
```

## Operations Reference

### `create`

| Arg | Type | Description |
|-----|------|-------------|
| `req` | Request | Contains authenticated `user` |
| `data` | object | Full document data being created |
| `siblingData` | object | Adjacent field data |

Returns `boolean`. `false` → value discarded silently.

### `read`

| Arg | Type | Description |
|-----|------|-------------|
| `req` | Request | Contains authenticated `user` |
| `id` | string | Document ID being read |
| `doc` | object | Full document data |
| `siblingData` | object | Adjacent field data |

Returns `boolean`. `false` → field **omitted** from response.

### `update`

| Arg | Type | Description |
|-----|------|-------------|
| `req` | Request | Contains authenticated `user` |
| `id` | string | Document ID being updated |
| `data` | object | Full incoming update data |
| `siblingData` | object | Adjacent field data |
| `doc` | object | Full document **before** update |

Returns `boolean`. `false` → update silently ignored, existing value preserved.

## Gotchas

- **No Query constraints** — field access cannot return a MongoDB/Drizzle Query object to filter rows. Use [[wiki/tech-patterns/payload-cms-collection-access-control|Collection Access Control]] for row-level security.
- **Silent discard** — denied `create`/`update` does NOT throw; always check response to confirm value was accepted.
- **`read: false` = omission** — the field key is absent in the response, not `null`. Client code must handle missing keys.
- Field access runs **after** collection-level access — collection must allow the operation first.

## Related

- [[wiki/tech-patterns/payload-cms-collection-access-control|Collection Access Control]] — operation-level + row-level security via Query return
- [[wiki/tech-patterns/payload-cms-installation|Payload CMS Installation & Setup]] — config basics

## Sources

- `raw/access-control__fields.md` — compiled 2026-05-15
- Upstream: https://payloadcms.com/docs/access-control/fields
