Relations 

Relations are resolved automatically from the TCA schema. By default, related records are serialized as plain IRI strings:

{
    "color_id": "/_api/colors/1",
    "categories": [
        "/_api/categories/5",
        "/_api/categories/8"
    ]
}
Copied!

This format is compatible with API Platform Admin and other Hydra clients that resolve IRIs on demand. To inline the full related record instead, see Embedding related records below.

An IRI is only rendered when it dereferences, i.e. when the related table has a registered resource with the show operation. A relation to any other table renders the related record's uid instead ("color_id": 1). In a group column that may reference several tables, where a bare uid would not say which table it belongs to, such an item renders as {"@type": "…", "uid": 1}.

Controlling recursion depth 

Control recursion depth with 'embed' => ['depth' => 2]. The default depth is 1.

'columns' => [
    'color_id' => ['embed' => ['depth' => 2]],
],
Copied!

Multi-table group fields 

TCA type=group fields with multiple allowed tables store relations in the prefixed tablename_uid format (e.g. pages_1,sys_file_3). These fields support embedding just like single-table relations:

// TCA definition
'related_items' => [
    'label' => 'Related Items',
    'config' => [
        'type' => 'group',
        'allowed' => 'tx_myext_domain_model_article,tx_myext_domain_model_color',
    ],
],

// API resource configuration
'columns' => [
    'related_items' => ['groups' => ['list', 'show'], 'embed' => true],
],
Copied!

The embedded response contains full objects from different tables, each with its own @type and fields:

{
    "related_items": [
        {
            "@id": "/_api/articles/201",
            "@type": "Article",
            "uid": 201,
            "title": "Related Article"
        },
        {
            "@id": "/_api/colors/1",
            "@type": "Color",
            "uid": 1,
            "name": "Red"
        }
    ]
}
Copied!

Each referenced table is resolved to its registered API resource. When no resource is registered for a table, a default-mode config is synthesized automatically (all TCA columns exposed). Its @type is derived from the table name (tx_news_domain_model_tag → TxNewsDomainModelTag) unless the column sets resourceType.

An embedded record that has no item endpoint — from an unregistered table, or from a resource without the show operation — carries no @id. It is a JSON-LD blank node: an @id would point at a URL that answers 404.

Bulk preloading is fully supported: collection requests issue one SELECT per referenced table regardless of how many parent rows exist, avoiding N+1 queries.

Writing nested objects 

When a create or update request contains a related record as an object (rather than a UID), TCA_API creates the child record in-line. The child table must have a registered resource in the API registry; a nested object for an unregistered table is rejected with 422 and an UNRESOLVABLE_RELATION violation naming the column and the table.

Passing an existing UID or IRI never needs a registered child resource, so linking is unaffected.

Nested writes without a child resource 

A child table that should not be reachable as a resource of its own does not need one. Declare nestedWrite on the parent column instead — it states the role required to create through that relation, and the child's shape is derived from TCA:

use MaikSchneider\TcaApi\Enum\AccessRole;

'columns' => [
    'note_id' => [
        'groups'      => ['list', 'show', 'create'],
        'nestedWrite' => AccessRole::FE_USER,
    ],
],
Copied!

nestedWrite accepts the same shapes as a security entry: an AccessRole, an [AccessRole, groupIds] tuple, or a [class-string, method-string] callable.

When the child table is registered, nestedWrite still wins for the access check — who may write through a relation is a narrower question than what the child resource permits on its own endpoint — while columns, validation and ownership continue to come from the registered resource.

{
    "title": "My Article",
    "color_id": { "name": "Red" }
}
Copied!

By default the child resource is looked up by the foreign table name from TCA. When multiple resources are registered for the same DB table, use the resourceName column option to pin the lookup to a specific resource:

'columns' => [
    'color_id' => [
        'groups'       => ['list', 'show', 'create', 'update'],
        'resourceName' => 'colors',   // look up "colors" resource, not by table
    ],
],
Copied!

This also controls which resource's security.create role is enforced on the nested write and which resource's column config is used for validation.

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 Yes
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