4ALLPORTAL4ALLPORTAL
All ideas

Objects API: implement embed to load related objects in one request

Summary The Objects API already documents an embed query parameter and an embedded response property — on both GET /modules/{module}/objects and GET /modules/{module}/objects/{id}. The server currently ignores it: the parameter is accepted, no error is returned, and the response simply comes back without embedded. Every integration that needs related records therefore has to resolve relations itself. Please implement embed as the published OpenAPI contract describes it. The problem today Relations are resolvable client-side, so this is about cost, not impossibility. Fetching a collection's assets with filename and file size takes two requests plus join code: GET /modules/fcollectionitem/objects?query=collection = '' → relation records holding file IDs GET /modules/file/objects?query=id IN ('9f2c…','4b81…',…)&fields=id,name,filesize …then match the two result sets by ID in the client. (Written naively — one request per relation record — it's 51 requests, which is what a fair amount of integration code does in practice.) That is perfectly workable for one level. The cost shows up as soon as it isn't: Each relation level is a dependent round trip. Request 2 cannot start until request 1 returns, because request 1 produces the IDs. Asset → collection item → collection → owner is four sequential calls. With embed it is one. Every client re-implements the same join. Collect IDs, dedupe, query, build a lookup map, stitch results back onto the parents — in every integration, in every language. ID lists don't scale into a URL. A few hundred UUIDs in id IN (...) runs into URL length limits, so the join also needs chunking and cross-chunk merging. Per-branch field selection is lost. fields applies to the whole second request. Two relation fields pointing at the same module but needing different fields means splitting into two more requests. Reverse relations still need the hop. Listing a collection's assets from the collection side requires querying the relation module first; batching IDs doesn't remove that step. Proposed behaviour GET /modules/fcollectionitem/objects ?query=collection = '' &embed=file[id,name,filesize] { "result": [ { "id": "1a7b…", "module": "fcollectionitem", "file": [{ "value": "9f2c…" }], "_embedded": { "file": [ { "id": "9f2c…", "module": "file", "name": [{ "value": "hero.jpg" }], "filesize": [{ "value": 184320 }] } ] } } ] } Specifically: Both endpoints — list and single object. Comma-separated relation fields: embed=created_by,file. Bracket field selection to prevent overfetching: created_by[id,firstname,lastname]. Nested embedding as a follow-up step: file[id,name,created_by[id,firstname]]. This is where the manual join hurts most, so it carries much of the value. Permissions and dimensions apply unchanged. Embedded objects pass the same read-permission and dimension filtering as a direct request. Objects the user may not read are omitted from _embedded rather than failing the whole request, and the dimensions parameter applies to embedded objects as well. Documented guard rails — a maximum nesting depth and a maximum number of embedded fields per request, with a clear 400 when exceeded, so one request cannot turn into an unbounded join. Reverse relations would be valuable as a later extension: embedding a collection's items from the collection object, with a sane limit. Why this matters Dependent round trips collapse. One sequential request per relation level becomes one request total, plus no chunking when ID lists get long. That is measurable on customer-premise and colocated deployments over a WAN, and directly visible in page-load times for headless frontends and e-commerce channels. Integration code shrinks. The dedupe/chunk/map/stitch layer disappears from every client that talks to the API. Better queries on the server. A server-side join instead of round-tripping large IN lists that the client assembled — fewer statements, and no multi-hundred-element IN clauses to plan. The contract already promises it. embed and _embedded are part of the public OpenAPI spec for the Objects API, and embed already works elsewhere in the API (POST /auth/login?embed=modules,features,presets, GET /system/apps/active?embed=updates). Integrators reasonably assume it works for objects too — and because the parameter is silently ignored, they find out only by inspecting a response that is missing data. Consistency. One embedding concept across the whole API, instead of one that works in some corners and not in the one that matters most. Smallest useful first step If the full feature is a larger effort, two things would already help: Single-level embed with field selection on both objects endpoints — that removes the hand-written join for the common case. Until then, fail loudly. Returning 400 for an unsupported embed value beats silently returning unembedded data, which currently looks like a bug in the client. Who benefits Anyone building against the API: headless frontends and custom portals, PIM syndication and channel exports, e-commerce integrations, and middleware that mirrors 4ALLPORTAL data into other systems.