Write privilege model
Write operations (create, update, delete) pass through a write
privilege model that sits between the access check (Security) 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
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. |
'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 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
The acting user is resolved from the request in a fixed order:
- Frontend user — a
frontend.userrequest attribute with a non-emptyuidyieldsactorType = 'fe_user'. - Backend user — an authenticated
$GLOBALSyields['BE_ USER'] actorType = 'be_user'. - No user — falls back to
actorType = 'system', and system mode is forced regardless of the configuredwriteMode.
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:
_tca_api[fe_user:42:johndoe]
_tca_api[be_user:1:admin]
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
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
The built-in list can be extended, never reduced. Configure
\Maik in your extension's
Configuration/:
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.
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:
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:
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.