Download the PHP package maikschneider/tca-api without Composer
On this page you can find all versions of the php package maikschneider/tca-api. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download maikschneider/tca-api
More information about maikschneider/tca-api
Files in maikschneider/tca-api
Package tca-api
Short Description This package provides an REST API based on the TYPO3 TCA — exposes database tables as Hydra JSON-LD resources.
License GPL-2.0-or-later
Homepage https://extensions.typo3.org/extension/tca_api
Informations about the package tca-api
State: Beta (0.2.0) — feedback and contributions very welcome. See GitHub Discussions to help validate the architecture, security model, and design decisions before they stabilize.
Motivation
TYPO3 offers several existing approaches for serving content as structured data. TCA_API was built to fill a gap where other API extensions fall short: exposing multiple resources uniformly, with minimal boilerplate and strong query efficiency.
See the Motivation chapter in the documentation for the full comparison.
Features
- Full CRUD — List, show, create, update and delete operations
- Hydra JSON-LD — Responses follow the Hydra specification (
application/ld+json) - Configuration-driven — Expose tables by registering a PHP configuration array; no custom controllers needed
- Serialization groups — Use
groupsto control which columns appear per operation - Filtering — Exact, partial, word-start, range, full-text search, and many-to-many filter strategies via query parameters; configurable defaults and private (non-overrideable) filters; extensible via
FilterInterface - Sorting — Configurable allowed sort columns with defaults
- Pagination — Offset-based pagination with Hydra
PartialCollectionViewlinks - Validation — Required, maxLength, minLength, and regex validators with structured 422 error responses
- File uploads —
multipart/form-datafile uploads on write endpoints with per-column FAL storage, size limits, and filename masks - Access control — Per-operation roles:
PUBLIC,FE_USER,FE_GROUP,BE_USER,BE_ADMIN,OWNER(record-level ownership), or custom callables - Write privilege model — Actor-aware write context with configurable execution strategy, per-table access control, system-table deny list, and structured audit logging
- Relation handling — Plain IRI strings or fully embedded related records (configurable depth); create new related records inline on POST/PUT/PATCH
- Userinfo endpoint — Expose the authenticated FE user's own record at a configurable URL
- OpenAPI + Swagger UI — Auto-generated OpenAPI 3.1.0 spec and interactive Swagger UI served directly from the API prefix
- API Platform compatible — Responses follow the Hydra JSON-LD spec and work with API Platform and its ecosystem
- PSR-14 events — Hook into the request lifecycle with Before/AfterOperation and Before/AfterWrite events
- TYPO3 DataHandler — Write operations use TYPO3's DataHandler for safe, consistent data manipulation
- Response caching — Tag-based HTTP response caching for
listandshowoperations with automatic invalidation via the TYPO3 DataHandler hook; configurable TTL and per-request bypass - Multi-language — URL base segments (
/de/api/...) and an optionalX-Localeheader resolve the TYPO3SiteLanguage; queries constrain onsys_language_uid, translations overlay default-language rows honouring the site'sfallbackType,sys_language_uid = -1records stay visible, and cache keys are language-scoped - Extensible handler pipeline — Register custom operation handlers or override built-in ones from any extension
- TCA-style overrides — Override or extend any resource config shipped by a third-party package via
Configuration/TcaApi/Overrides/— mirrors TYPO3's$GLOBALS['TCA']+TCA/Overrides/pattern
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.2 |
| TYPO3 | ^13.4 || ^14.3 |
Demo
A demo project showcasing various TCA_API configurations is available at maikschneider/typo3-petstore. It serves as the reference for different resource setups (currently in early stages).
Installation
Site set
The extension ships a TYPO3 site set (maikschneider/tca-api). Add it to your site's config/sites/<site>/config.yaml:
This exposes the following site settings, configurable per site in the TYPO3 backend under Site Management → Sites → Settings:
| Setting | Default | Description |
|---|---|---|
tca_api.enabled |
true |
Enable or disable the API for this site |
tca_api.apiPrefix |
/_api/ |
URL prefix for all API endpoints |
tca_api.defaultItemsPerPage |
20 |
Default page size for collection responses |
tca_api.allowedResources |
(empty — all) | Comma-separated list of resource names to expose; empty allows all |
tca_api.debugMode |
false |
Return verbose error details in responses |
tca_api.openApiExposed |
PUBLIC |
Who may access the OpenAPI spec (PUBLIC, FE_USER, BE_USER, BE_ADMIN, NONE) |
tca_api.apiSpecTitle |
TCA_API |
Title shown in the OpenAPI spec and Swagger UI |
tca_api.apiSpecDescription |
(empty) | Description shown in the OpenAPI spec and Swagger UI |
tca_api.apiSpecVersion |
1.0.0 |
Version string in the OpenAPI spec info block |
tca_api.swaggerUiEnabled |
PUBLIC |
Who may access the Swagger UI (PUBLIC, FE_USER, BE_USER, BE_ADMIN, NONE) |
tca_api.corsEnabled |
false |
Enable CORS support — adds headers to all API responses and handles OPTIONS preflight requests with a 204 response |
tca_api.corsOrigin |
* |
Value for Access-Control-Allow-Origin |
tca_api.corsAllowCredentials |
false |
Include Access-Control-Allow-Credentials: true header (required when the client sends cookies or Authorization headers cross-origin) |
Quick start
1. Create the resource configuration
Place a PHP file in Configuration/TcaApi/ inside any active TYPO3 extension. No manual registration is needed — the extension auto-discovers all *.php files from every active package's Configuration/TcaApi/ directory at boot time and caches the result.
Zero-config (read-only): the three required keys are enough. operations defaults to ['list', 'show'] and both are PUBLIC by default — no security block needed:
To enable write operations, add them explicitly:
Explicit mode (opt in by adding groups to any column — only columns with groups are then exposed):
2. Use the API
All resources are served under the /_api/ prefix:
Overriding third-party resource configs
Any active extension can modify a resource configuration defined by another extension — without forking it. This mirrors TYPO3's $GLOBALS['TCA'] + TCA/Overrides/ pattern.
Place a PHP file in Configuration/TcaApi/Overrides/ of any active extension. The file receives $GLOBALS['TCA_API'] populated with all base configs and can manipulate it freely:
Override files within a package run in alphabetical filename order. Across packages they run in TYPO3 package load order. Last write wins.
OpenAPI spec & Swagger UI
The extension generates a live OpenAPI 3.1.0 JSON spec from the registered resources and exposes two additional endpoints:
| Endpoint | Description |
|---|---|
{apiPrefix}openapi.json |
Machine-readable OpenAPI spec (e.g. /_api/openapi.json) |
{apiPrefix}swagger-ui |
Interactive Swagger UI (e.g. /_api/swagger-ui) |
Access to both endpoints is controlled by the tca_api.openApiExposed and tca_api.swaggerUiEnabled site settings respectively. Both default to PUBLIC.
API Platform
TCA_API responses are compatible with API Platform and its ecosystem. A bundled React-based admin UI is available via the maikschneider/api-platform-admin site set — add it to your site's dependencies to activate it. This UI is intended for testing during extension development and may be removed in a future version.
Configuration reference
General
| Key | Description |
|---|---|
table |
TYPO3 database table name |
resourceName |
URL slug used in /_api/{resourceName} |
resourceType |
JSON-LD @type value |
type |
Set to 'userinfo' to create a userinfo endpoint |
operations |
Array of enabled operations: list, show, create, update, delete; defaults to list and show if not set |
itemsPerPage |
Default page size for list operations |
maxItemsPerPage |
Upper limit for itemsPerPage; when set, the requested page size is clamped to this value. No limit when omitted |
storagePid |
Page ID for newly created records |
writeMode |
Write execution strategy: acting_user (default) or system_admin — see Write privilege model |
Column visibility
TCA_API has two visibility modes. The mode is auto-detected per resource:
Default mode — active when no column has groups set. All non-system TCA columns (i.e. not hidden, deleted, tstamp, crdate, language/workspace fields) are automatically exposed for read and write.
Explicit mode — active as soon as any column declares groups. Only columns with a matching groups entry are exposed; all others are hidden.
Serialization groups
Use groups instead of readable/writable to control visibility per operation:
Valid group names: list, show, create, update.
Columns reference
Each entry in columns maps to a database column. All keys are optional:
| Key | Description |
|---|---|
type |
Data type hint for OpenAPI schema (e.g. string, integer) |
readable |
true — include in responses (legacy; use groups instead) |
writable |
true — accept in create/update requests (legacy; use groups instead) |
groups |
Array of operations where this column is active — triggers explicit mode (list, show, create, update) |
required |
Require on POST/PUT (skipped on PATCH if absent) |
embed |
true or ['depth' => N] — inline related records instead of IRI strings |
resourceName |
Override related resource name for relation columns |
processor |
Column processor class (does not trigger explicit mode) |
callback |
[ClassName::class, 'method'] invoked after all columns and relations are resolved; its return value replaces the column's value. See Column callbacks |
validators |
Array of validation rules (see Validation) |
upload |
Enable file uploads for this column via multipart/form-data; must include at least folder (FAL storage ref, e.g. 1:/uploads/). See File uploads |
image |
Image processing options for ImageProcessor columns — controls dimensions, crop variant selection, format conversion, and URL mode. See Image processor config |
Field type support
The serializer automatically handles all TYPO3 TCA field types. Relational types are resolved via dedicated serializers; scalar types that store encoded data are decoded before output; sensitive types are excluded.
| TCA type | Handling | Output |
|---|---|---|
file |
FileFieldSerializer — auto-selects ImageProcessor or FileProcessor |
Processed file/image object(s) |
category |
RelationSerializer |
IRI string or embedded record |
select (relational) |
RelationSerializer |
IRI string or embedded record |
inline |
RelationSerializer |
IRI string or embedded record |
group |
GroupFieldSerializer |
IRI string or embedded record |
json |
Auto-decoded via json_decode |
Decoded array/object |
imageManipulation |
Auto-decoded via json_decode |
Decoded crop config object |
flex |
Auto-decoded via GeneralUtility::xml2array |
Decoded associative array |
datetime |
Auto-formatted to ISO 8601 (DateTimeInterface::ATOM) |
"2024-01-01T00:00:00+00:00" or null |
link |
Auto-applies TypoLinkProcessor |
Resolved public URL string |
password |
Excluded — never appears in API responses | (column omitted) |
input, text, number, email, color, country, slug, radio, select (static) |
Raw DB value | String, integer, or appropriate scalar |
check |
Raw DB value | Bitmask integer |
language |
Excluded by TcaColumnDiscovery via ctrl.languageField |
(column omitted) |
folder, none, passthrough, user |
Raw DB value | Implementation-defined |
An explicit processor on a column definition always overrides the automatic handling described above.
Column callbacks
A callback is a lightweight alternative to a processor for post-processing a single column. Unlike a processor — which receives only the raw column value — a callback runs after every column and relation has been resolved, receives the fully serialized row plus the raw DB row, and its return value replaces the column's value:
The signature is (array $serializedRow, array $rawRow): mixed. The class is built via GeneralUtility::makeInstance(), so constructor DI works. Because callbacks run after relation resolving — and before virtual properties — they can read embedded relations, processor output, and sibling columns from $serializedRow, and a virtual property can in turn build on a column's callback result. Callbacks honour the column's visibility (groups) and sparse-fieldset (?fields[]=…) gates. The same callback key is also available on virtual properties.
Filters
Each filterable column maps to a filter class. Use the shorthand (class name only) or the options form (two-element array with class + config):
Built-in filter classes
| Class | Description | Options |
|---|---|---|
ExactFilter |
WHERE column = value |
— |
PartialFilter |
WHERE column LIKE %value% |
— |
WordStartFilter |
WHERE column LIKE value% |
— |
RangeFilter |
Comparison operators on a column (numeric, string or date) | value must be ['gte'=>…, 'lte'=>…, 'gt'=>…, 'lt'=>…]. The DBAL parameter type is inferred from the column's TCA (number, datetime, …); the optional type (int|float|string|date|datetime) overrides it |
SearchFilter |
OR across multiple columns (LIKE) |
columns (required), match (partial|word_start, default partial) |
MmFilter |
Subquery via MM intermediate table | mm_table, mm_local_key, mm_foreign_key, mm_constraints (derived from TCA when omitted) |
For MmFilter, if the options array is omitted the extension derives the MM config from TCA automatically (requires a valid MM key on the field).
Default values and private filters
Two options available on any filter definition control server-side defaults and enforcement:
| Option | Type | Description |
|---|---|---|
default |
string |
Value applied when the filter is absent from the request URL params |
private |
bool |
When true, the default always applies — user-supplied values are ignored; the filter is also excluded from the OpenAPI spec |
A private filter without a default has no effect (no value to enforce).
Filter query syntax
Plain top-level parameters are the primary style and are advertised by the hydra:search block:
The bracket notation is also accepted (required for RangeFilter sub-keys and legacy clients):
When both styles are present for the same column, the bracket form wins. Only columns declared as filters in the resource configuration are matched; other top-level parameters are ignored.
Search filter example
Sorting
Security
Assign an access role per operation. list and show default to PUBLIC; write operations default to DISABLED and must be explicitly configured:
Defaults
Write operations are disabled by default. You must explicitly configure security for create, update, and delete:
| Operation | Default |
|---|---|
list |
PUBLIC — accessible without authentication |
show |
PUBLIC — accessible without authentication |
create |
DISABLED — returns 403 until explicitly configured |
update |
DISABLED — returns 403 until explicitly configured |
delete |
DISABLED — returns 403 until explicitly configured |
Available roles
| Role | Description |
|---|---|
PUBLIC |
No authentication required |
DISABLED |
Always denied — used as the default for write operations |
FE_USER |
Any authenticated frontend user |
FE_GROUP |
Any FE user with at least one group; use [AccessRole::FE_GROUP, [1,2]] to restrict to specific group IDs |
BE_USER |
Any authenticated backend user |
BE_ADMIN |
Backend admin only |
OWNER |
Authenticated FE user whose UID matches the record's ownership column (see Ownership) |
You can also use a callable for fully custom logic:
Ownership
The ownership section enables declarative record-level security for write operations. It pairs with AccessRole::OWNER in the security config.
What this does:
- On create — the
fe_user_idcolumn is automatically set to the authenticated FE user's UID server-side. The client cannot supply this value; it is stripped from the request body regardless of the column'sgroupsconfig. - On update/delete — the record's
fe_user_idis compared to the current FE user's UID. If they don't match the request returns 403.
Separate tracking vs. auth columns
Use setOnCreate when you want an additional tracking column alongside the auth column:
On create, both column and setOnCreate receive the FE user UID. column must be populated for OWNER auth to work on subsequent update/delete; setOnCreate provides an immutable "created by" audit trail in a separate DB column.
BE_ADMIN bypass
Backend admins bypass ownership checks by default. Set beAdminBypass: false to enforce ownership for admins too:
Ownership config reference
| Key | Required | Default | Description |
|---|---|---|---|
column |
when using OWNER |
— | DB column holding owner UID; compared on update/delete |
setOnCreate |
no | same as column |
Column auto-set on create (if different from column) |
beAdminBypass |
no | true |
When true, BE_ADMIN skips the ownership check |
Behaviour notes
AccessRole::OWNERwithoutownership.columnconfigured → 403 (fail-secure)- Unauthenticated request +
OWNER→ 403 (no FE user found) setOnCreatewithout a logged-in FE user → column is not set (no injection if user is null)- Ownership columns are always stripped from client input regardless of
groupsconfig OWNERis only meaningful onupdateanddelete; using it onlist/showwill always deny (no single record to compare against)
Caching
Enable tag-based HTTP response caching for list and show operations per resource:
How it works
- On a cache miss: the response body is stored with per-record tags (
{table}_{uid}). Two headers are added:X-TCA-API-Cache: MISSandX-Cache-Tags: articles_1,articles_2. - On a cache hit: the stored body is returned immediately with
X-TCA-API-Cache: HIT. No handler or serializer runs. - Bypass: when any parameter listed in
parametersToIgnoreis present in the request (top-level or nested underfilters[…]), the cache is skipped entirely for that request.
Cache invalidation
Invalidation fires automatically via a DataHandler clearCachePostProc hook. When a record is saved or deleted through the TYPO3 backend, all cached responses tagged for the affected table are flushed.
Note: Write operations performed through the API itself (
create,update,delete) do not trigger this hook. Cachedlist/showresponses will remain valid until the next DataHandler operation touches the same table or the TTL expires. If your application writes records via the API and reads them back immediately, configure a shortlifetimeor disable caching for that resource.
Cache backend
By default the tca_api cache uses Typo3DatabaseBackend (stored in cf_tca_api / cf_tca_api_tags database tables). For production use, configure a faster backend in config/system/additional.php:
Multi-site note
Cache keys are scoped by resource name and query parameters but not by TYPO3 site. In multi-site setups where two sites use the same resource name (articles) with different storagePid or different records, their cache entries will collide. Use distinct resourceName values per site or disable caching until this is addressed.
Caching config reference
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
false |
Enable caching for this resource |
lifetime |
int |
86400 |
Cache TTL in seconds. Must be a positive integer |
parametersToIgnore |
string[] |
[] |
Query parameters whose presence bypasses the cache entirely |
Write privilege model
Write operations (create, update, delete) pass through a write privilege model that controls execution strategy, enforces table-level access control, and logs every mutation with the actor's identity.
Write modes
Each resource has a configurable write mode that determines how writes are executed:
| Mode | Value | Description |
|---|---|---|
| Acting user | acting_user |
Default. Tracks the real authenticated user (FE or BE) for audit purposes. The actor's identity is embedded in the DataHandler username. |
| System admin | system_admin |
Opt-in. Uses a synthetic backend admin (uid=0, admin=1) with no user identity. Only for trusted, internal APIs. |
Configure per resource in the general section:
To opt into system admin mode for an internal resource:
⚠ Warning:
system_adminmode bypasses TYPO3 access control. Only enable for internal APIs where the calling application is fully trusted.
Actor resolution
The write context resolves the acting user automatically from the request:
- Frontend user — if
frontend.useris present with a valid UID →actorType = 'fe_user' - Backend user — if
$GLOBALS['BE_USER']is authenticated →actorType = 'be_user' - No user — falls back to
actorType = 'system'with system admin mode forced
In acting_user mode, the DataHandler's internal username encodes the real actor for traceability:
Table access control
A built-in table access control layer prevents writes to security-sensitive system tables. This applies regardless of write mode.
Denied tables (built-in)
The following tables are always blocked from API writes:
| Table | Reason |
|---|---|
be_users |
Backend user 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 audit log |
Custom allow/deny lists
Extend the table access control via Services.yaml:
Precedence: deny list always wins over allow list. A table in both lists is denied.
Audit logging
Every write operation is logged via PSR-3 with structured context data:
Denied writes are logged at WARNING level:
Logs are written to TYPO3's logging framework and can be routed to any PSR-3 compatible handler.
Validation
Configure validators per column:
| Type | Parameters | Description |
|---|---|---|
maxLength |
max (int) |
Maximum string length |
minLength |
min (int) |
Minimum string length |
regex |
pattern (string) |
PCRE pattern to match |
Validation failures return 422 Unprocessable Entity:
File uploads
Columns with an upload key accept files via multipart/form-data on POST, PUT, and PATCH endpoints. Files are stored in a FAL storage folder and attached as sys_file_reference records.
Configuration
Add an upload key to any type=file TCA column:
Allowed file extensions are not set here — they come from the TCA column's allowed config (e.g. 'allowed' => 'jpg,jpeg,png').
Upload config reference
| Key | Required | Default | Description |
|---|---|---|---|
folder |
Yes | — | FAL storage reference, e.g. 1:/uploads/. Must start with a storage UID followed by :/ |
maxSize |
No | unlimited | Max file size. Accepts an integer (bytes) or a string: 5M, 100K, 1G |
duplication |
No | rename |
Collision handling: rename (add suffix), replace (overwrite), cancel (reject) |
filenameMask |
No | (original filename) | Filename template with placeholders: {name} (base name), {extension} (ext without dot), {ext} (ext with dot), {contentHash} (MD5 of file), {nameHash} (MD5 of name), {timestamp} (Unix time), {unique} (random ID). Example: {timestamp}_{unique}{ext} → 1714000000_abc123.jpg |
Sending requests
Validation errors
Upload violations return 422 Unprocessable Entity:
| Code | Meaning |
|---|---|
UPLOAD_ERROR |
PHP transport error (upload aborted, temp dir issue, etc.) |
UPLOAD_MIME_TYPE |
MIME type not permitted for this column |
UPLOAD_MAX_SIZE |
File exceeds the configured maxSize |
UPLOAD_EXTENSION |
File extension not in the TCA allowed list |
Relations
Relations are resolved automatically from the TCA schema. By default, related records are serialized as plain IRI strings:
This format is compatible with API Platform Admin and other Hydra clients that resolve IRIs on demand.
Embedding related records
Add 'embed' => true to a column to inline the full related record instead of an IRI string:
In default mode, embed alone is enough — it does not trigger explicit mode:
Response with embedded data:
Control recursion depth with 'embed' => ['depth' => 2]. The default depth is 1.
The related resource must be registered in the ApiRegistry for embedding to work.
Supported relation types
| TCA type | Default output | Embedding |
|---|---|---|
select / group |
UID list (1,2,3) — single table → IRI strings |
Yes |
select / group |
Prefixed list (table_uid) — multi-table → IRI strings |
No |
inline / select |
foreign_field back-reference → IRI strings |
Yes |
Any + MM |
Intermediate MM table → IRI strings | Yes |
type=group + MM |
Column holds count, relations in MM → IRI strings | Yes |
Creating related records on write
On POST, PUT, and PATCH you can create new related records inline by passing an assoc array instead of a UID. Three forms are accepted per relation field:
These forms work across the supported TCA relation types, with one important limitation for group fields noted below:
| TCA type | Example |
|---|---|
select + single value |
"color_id": { "name": "Red" } |
category / select + MM |
"categories": [1, { "title": "New" }] |
group (single-table allowed) |
"tags": [3, { "name": "New Tag" }] |
inline + foreign_field |
"related_items": [{ "name": "Child" }] |
For type=group, inline creation of new related records is only supported when allowed contains exactly one table. Multi-table group relations still support linking existing records by UID, but not creating new related records inline.
Atomicity: all records (parent + new children) are created in a single DataHandler call, so cross-references resolve correctly and no partial writes occur.
Ownership: if the related table has an ownership config, the ownership column is automatically injected and cannot be overridden by the client.
Security gate: new sub-records can only be created for tables that have their own entry in ApiRegistry. Objects for unregistered foreign tables are silently skipped; existing UID references still work.
Inline relations (type=inline + foreign_field)
Inline children carry a back-pointer column to the parent (foreign_field). The extension sets this column automatically via DataHandler's writeForeignField mechanism — you do not need to include the parent UID in the child object:
On PATCH, new inline children are appended; existing children are left untouched.
Virtual properties
Virtual properties are computed fields appended to the serialized output. They appear after all real columns (and after column callbacks) and can be driven by a callable, a column processor, or a file/image column reference.
A callback is not mutually exclusive with a processor or file reference: when both are present the processor/file produces the base value and the callback runs last as a final transform. A virtual-property callback always runs — after all columns, column callbacks, and its own base value — so it can read any column or earlier virtual property from $serializedRow.
Callable
The callable receives (array $serializedRow, array $rawRow) and returns any serializable value. $serializedRow reflects columns already serialized in this request (including their callbacks); $rawRow is the raw DB record. When a processor or file column is also set, the callback receives that resolved base value under the property's key in $serializedRow and may transform it.
Column processor
The processor implements ColumnProcessorInterface::process(mixed $value, array $config, array $context). Without a column key the value passed is null.
Referencing an existing column
Add a column key to source the virtual property's value from an existing DB column:
The processor receives the column's raw DB value instead of null. For file/image columns the file references are fetched automatically and the result of the virtual property's own file processor is returned — this lets you expose the same image at different sizes per operation:
The virtual property uses its own image config — the referenced column's original config is ignored.
Image processor config
The image key is an array with the following options. All are optional:
| Key | Type | Description |
|---|---|---|
width |
string |
Target width. Accepts plain integer ("400"), crop-scale ("400c"), or scale-down-only ("400m") |
height |
string |
Target height — same notation as width |
minWidth |
int |
Minimum width in pixels (positive integer) |
minHeight |
int |
Minimum height in pixels (positive integer) |
maxWidth |
int |
Maximum width in pixels (positive integer) |
maxHeight |
int |
Maximum height in pixels (positive integer) |
cropVariant |
string |
Crop variant identifier. When set, only that variant is processed and the URL is inlined as publicUrl (no cropVariants key). When omitted, all variants are returned as a cropVariants map |
fileExtension |
string |
Target file extension for format conversion (e.g. 'webp') |
absolute |
bool |
Force an absolute URL. Default: false |
Output modes:
Without cropVariant (or cropVariant omitted) — all variants returned:
With cropVariant set — single variant inlined:
Visibility gate
Virtual properties respect the same serialization groups as regular columns. When any column has a groups key (explicit mode), virtual properties without groups are excluded:
Userinfo endpoint
A userinfo endpoint exposes the currently authenticated FE user's own record without requiring a UID in the URL. Set 'type' => 'userinfo' in the general section:
Behaviour:
- Only
GETis allowed — write operations are not supported on userinfo endpoints. - Returns 403 if no FE user is authenticated.
- All column features work as normal:
embed,virtualProperties, column processors (see Virtual properties). - The
securityandoperationskeys are ignored — access is always tied to FE user authentication.
Events
The extension dispatches PSR-14 events throughout the request lifecycle:
| Event | Fired | Use case |
|---|---|---|
BeforeOperationEvent |
After access check, before handler | Abort operation, modify request |
AfterOperationEvent |
After handler, before response | Add computed fields, transform response data |
BeforeWriteEvent |
Before DataHandler create/update/delete | Validate or modify data before persistence |
AfterWriteEvent |
After DataHandler operations | Trigger side effects (cache clear, logging) |
Example listener registration in Configuration/Services.yaml:
Custom operation handlers
The dispatcher routes each request through a handler pipeline — a prioritised list of objects that implement OperationHandlerInterface. Built-in handlers cover list, show, create, update, delete, and userinfo. Third-party extensions can add new operation types or replace built-in behaviour by registering their own handlers.
Interface
Request attributes
Before the handler loop, the dispatcher sets the following attributes on the PSR-7 request:
| Attribute | Type | Description |
|---|---|---|
tca_api.uid |
int\|null |
UID from the URL segment |
tca_api.operation |
string |
Resolved operation name |
tca_api.fields |
array |
?fields[]=… sparse-fieldset param |
tca_api.page |
int |
Pagination page (≥ 1) |
tca_api.items_per_page |
int |
Items per page (clamped to maxItemsPerPage when configured) |
tca_api.filters |
array |
Merged filter values from ?filters[…]=… and top-level ?column=… params |
tca_api.order |
array |
Raw ?order[…]=asc\|desc params |
tca_api.partial |
bool |
true for PATCH (partial update) |
Writing a custom handler
Registering handlers
Register handlers in your extension's ext_localconf.php. The dispatcher iterates handlers highest priority first and dispatches to the first match, so setting a higher priority than the built-in 10 overrides a built-in handler for a given operation.
The HandlerRegistry uses TYPO3's DI container via GeneralUtility::makeInstance(), so constructor dependencies are injected automatically. The #[Autoconfigure(public: true)] attribute on the class is required for the container to expose the service.
Custom filters
Every filter strategy is a class that implements FilterInterface. The extension discovers all implementations automatically via Symfony DI — no Services.yaml registration is needed.
Interface
FilterContext is a typed readonly value object. Its properties are:
| Property | Type | Description |
|---|---|---|
value |
mixed |
Filter value from the request query string |
table |
string |
Resource table name |
column |
string |
Column name this filter is applied to |
options |
array |
Filter-specific options from the resource config |
request |
ServerRequestInterface\|null |
PSR-7 request — available in HTTP context; null in unit tests |
resourceConfig |
ApiDefinition\|null |
Full resource config — available in HTTP context; null in unit tests |
Use $context->option('key', $default) to read from options with a fallback default.
Using the custom filter
Declare it in the resource config using the class name directly:
To pass extra config to the filter, use the two-element array form:
That's all. The class is auto-tagged and auto-wired — no ext_localconf.php or Services.yaml changes required.
Development
This extension uses DDEV for local development, see CONTRIBUTING.md for setup instructions.