---
title: "Write privilege model"
manual: "TCA_API"
version: "2.2"
permalink: "https://docs.typo3.org/permalink/maikschneider/tca-api:write-privileges@2.2"
source: "Configuration/WritePrivileges.rst"
rendered: "2026-10-12T13:17:40+00:00"
---

# Write privilege model {#write-privileges}

Write operations (`create`, `update`, `delete`) pass through a write
privilege model that sits between the access check ([Security](https://docs.typo3.org/permalink/maikschneider/tca-api:security@2.2)) and the
TYPO3 DataHandler. It decides **under which identity** a write executes,
enforces a table-level policy that no resource configuration can weaken, and
records every mutation with the acting user.

## Write modes {#write-modes}

Each resource has a write mode, configured as `general.writeMode`:

| Mode | Behaviour |
| --- | --- |
| `acting_user` | **Default.** The DataHandler runs under the identity of the real authenticated user (frontend or backend), so TYPO3's own access control applies and the actor is preserved for auditing. |
| `system_admin` | Opt-in. The DataHandler runs under a synthetic backend administrator (`uid=0`, `admin=1`) with no user identity. |

```php
'general' => [
    'table'        => 'tx_myext_domain_model_internal',
    'resourceName' => 'internal-records',
    'resourceType' => 'InternalRecord',
    'operations'   => ['list', 'create'],
    'writeMode'    => 'system_admin',   // default: 'acting_user'
],
```

> [!WARNING]
> `system_admin` bypasses TYPO3 access-control restrictions on the write.
> The API-level [Security](https://docs.typo3.org/permalink/maikschneider/tca-api:security@2.2) roles still gate who may call the endpoint,
> but everything TYPO3 would otherwise refuse the user is permitted. Use it
> only where the calling application is fully trusted.

## Actor resolution {#actor-resolution}

The acting user is resolved from the request in a fixed order:

1.  **Frontend user** — a `frontend.user` request attribute with a non-empty
    `uid` yields `actorType = 'fe_user'`.
1.  **Backend user** — an authenticated `$GLOBALS['BE_USER']` yields
    `actorType = 'be_user'`.
1.  **No user** — falls back to `actorType = 'system'`, and system mode is
    forced regardless of the configured `writeMode`.

In `acting_user` mode the DataHandler's internal username encodes the
resolved actor, so TYPO3's own `sys_history` and log entries stay traceable
to a real account:

```text
_tca_api[fe_user:42:johndoe]
_tca_api[be_user:1:admin]
```

## Table access control {#table-access-control}

A table-level policy is enforced on every write, in **both** write modes, before
the DataHandler is reached. A blocked table returns **403 Forbidden**.

### Built-in deny list {#built-in-deny-list}

These tables are always blocked, and no configuration can unblock them:

| Table | Reason |
| --- | --- |
| `be_users` | Backend user accounts and credentials |
| `be_groups` | Backend permission groups |
| `be_sessions` | Active backend sessions |
| `fe_sessions` | Active frontend sessions |
| `sys_filemounts` | File system mount points |
| `sys_be_shortcuts` | Backend user shortcuts |
| `sys_action` | System actions |
| `sys_log` | System log |

### Narrowing or extending the policy {#narrowing-or-extending-the-policy}

The built-in list can be **extended**, never reduced. Configure
`\MaikSchneider\TcaApi\Security\TableAccessControl` in your extension's
[`Configuration/Services.yaml`](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/FileStructure/Configuration/ServicesYaml.html#file-extension-configuration-services-yaml):

```yaml
services:
  MaikSchneider\TcaApi\Security\TableAccessControl:
    arguments:
      # When non-empty, ONLY these tables are writable
      $allowList:
        - tx_myext_domain_model_article
        - tx_myext_domain_model_comment
      # Denied in addition to the built-in list
      $denyList:
        - pages
        - tt_content
```

An empty `$allowList` (the default) means "every table that is not denied".
Deny always wins: a table named in both lists is denied.

> [!NOTE]
> This policy is about *tables*, not about *who* may write. It is a backstop
> against a misconfigured resource definition exposing something dangerous —
> it does not replace the per-operation roles in [Security](https://docs.typo3.org/permalink/maikschneider/tca-api:security@2.2).

## Audit logging {#audit-logging}

Every write is logged through TYPO3's PSR-3 logging framework, so it can be
routed to any configured handler.

A permitted write is logged at `info` level as `TCA API write operation`:

```text
operation:      create
table:          tx_myext_domain_model_article
uid:            NEW_primary
actor_type:     fe_user
actor_uid:      42
actor_username: johndoe
write_mode:     acting_user
```

A write refused by the table policy is logged at `warning` level as
`TCA API write denied`, with the same actor context plus a `reason`:

```text
operation:      write
table:          be_users
actor_type:     fe_user
actor_uid:      42
actor_username: johndoe
write_mode:     acting_user
reason:         Table blocked by access control policy
```

The `uid` of a create is the DataHandler placeholder (`NEW_…`) rather than
the final record uid, because the entry is written before persistence.
