Recipe: read & update records through the content routes
Recipe: read & update records through the content routes
Recipe: read & update records through the content routes
Prompt exampleRead a record's fields, rename it, add it to a collection, and create a new record then populate it — all through the generic content routes, with the property keys mapped back to the entity map.
After search, content updating is the most-used surface. The content routes are the
platform's generic CRUD over any entity — the same three calls work for an asset, a
genre, a language, etc. The only things that change per entity are the
contentDefinitionId and the field names in properties.
Safety (Class C). The generic route can write to shared/global records, so it is non-prod only. When
contentDefinitionIdis theassetentity, thecontentIdmust be within your run-root anchor (then it behaves likeupdate_asset, Class A). Tear down what you create.
How fields map to the entity map
Everything keys off reference/schema.md (the entity map):
contentDefinitionId= the entity's id. Pick the entity row in the Entities table and copy itscontentDefinitionId(e.g.asset→3ff29f61-bd0b-4c17-b855-49db5a292aeb,language→e4b10c04-1878-4830-a115-e42d52705059).propertieskeys = that entity's field names. They are exactly the rows in the entity's In-scope entity fields table. Only fields marked writable = yes can be set; the type column tells you the value shape.- Reference / lookup fields take a
{id, description}shape. A field that points at another entity (e.g.assignedPerson, or onecollections/tags/relatedContentelement) is written as{"id": "<guid>", "description": "<label>"}. Multi-valuedlist<guid>fields are arrays of those objects. (A bare GUID is expanded into{id, description}by reading the target'sdisplayName.) - The record envelope returned by
get_contentis{contentId, contentDefinitionId, languageId, properties{...}}; the field bag lives underproperties.
Update semantics (read-modify-write)
update_content is a partial patch: the SDK GETs the live record, deep-merges your
properties onto it, then PUTs the whole body. So you pass only the fields you change.
- Scalars and objects are replaced only when they actually differ.
- Arrays/lists are element-wise replaced and truncated to the length you pass. To append to a multi-valued field you must read the current list and send existing + new; sending a shorter list deletes the tail.
Python
# Components used: get_content, update_content, create_content, delete_content, search.
def read_record(sdk, content_id, content_definition_id):
"""Return the record's properties bag (keyed by entity field names), or {}."""
rec = get_content(sdk, content_id, content_definition_id, None) or {}
return rec.get("properties", {})
def find_content_id(sdk, content_definition_id, master_id, language_id=None):
"""Resolve a record's contentId from its entity + business key (search)."""
flt = [
{"fieldName": "contentDefinitionId", "operator": "Equals", "values": content_definition_id},
{"fieldName": "masterId", "operator": "Equals", "values": master_id},
]
if language_id:
flt.append({"fieldName": "languageId", "operator": "Equals", "values": language_id})
items = (search(sdk, filters=flt, size=1) or {}).get("items", [])
return items[0]["id"] if items else None
def ref(target_id, description=None):
"""Wire shape for a reference/lookup field: {id, description} (description optional)."""
return {"id": target_id, "description": description} if description else {"id": target_id}
def rename_record(sdk, content_id, content_definition_id, new_name):
"""Patch a single scalar field by its entity field name (writable=yes only)."""
return update_content(sdk, content_id, content_definition_id, {"displayName": new_name})
def add_to_collection(sdk, content_id, content_definition_id, collection_id, label=None):
"""Append a collection ref WITHOUT truncating: read current, add, write back."""
current = read_record(sdk, content_id, content_definition_id).get("collections", [])
updated = current + [ref(collection_id, label)]
return update_content(sdk, content_id, content_definition_id, {"collections": updated})
def create_then_populate(sdk, content_definition_id, properties, language_id=None):
"""Create a blank record from an entity template, then populate it; return its contentId."""
blank = create_content(sdk, content_definition_id, language_id) or {}
content_id = blank.get("contentId")
update_content(sdk, content_id, content_definition_id, properties, language_id)
return content_id
def delete_record(sdk, content_id, content_definition_id):
"""Tear down a record created via create_content (Class C cleanup)."""
return delete_content(sdk, content_id, content_definition_id)JavaScript
// Components used: getContent, updateContent, createContent, deleteContent, search.
// search advanced opts ride the trailing opts object (see recipes/search-patterns.md).
export async function readRecord(sdk, contentId, contentDefinitionId) {
// Return the record's properties bag (keyed by entity field names), or {}.
const rec = (await getContent(sdk, contentId, contentDefinitionId, null)) || {};
return rec.properties || {};
}
export async function findContentId(sdk, contentDefinitionId, masterId, languageId = null) {
// Resolve a record's contentId from its entity + business key (search).
const flt = [
{ fieldName: "contentDefinitionId", operator: "Equals", values: contentDefinitionId },
{ fieldName: "masterId", operator: "Equals", values: masterId },
];
if (languageId) flt.push({ fieldName: "languageId", operator: "Equals", values: languageId });
const res = await search(sdk, null, flt, null, 1);
const items = res ? res.items : [];
return items.length ? items[0].id : null;
}
export function ref(targetId, description = null) {
// Wire shape for a reference/lookup field: {id, description} (description optional).
return description ? { id: targetId, description } : { id: targetId };
}
export async function renameRecord(sdk, contentId, contentDefinitionId, newName) {
// Patch a single scalar field by its entity field name (writable=yes only).
return await updateContent(sdk, contentId, contentDefinitionId, { displayName: newName });
}
export async function addToCollection(sdk, contentId, contentDefinitionId, collectionId,
label = null) {
// Append a collection ref WITHOUT truncating: read current, add, write back.
const current = (await readRecord(sdk, contentId, contentDefinitionId)).collections || [];
const updated = [...current, ref(collectionId, label)];
return await updateContent(sdk, contentId, contentDefinitionId, { collections: updated });
}
export async function createThenPopulate(sdk, contentDefinitionId, properties,
languageId = null) {
// Create a blank record from an entity template, then populate it; return its contentId.
const blank = (await createContent(sdk, contentDefinitionId, languageId)) || {};
const contentId = blank.contentId;
await updateContent(sdk, contentId, contentDefinitionId, properties, languageId);
return contentId;
}
export async function deleteRecord(sdk, contentId, contentDefinitionId) {
// Tear down a record created via createContent (Class C cleanup).
return await deleteContent(sdk, contentId, contentDefinitionId);
}Notes
- Find vs. read. Use
get_contentwhen you already hold thecontentId. When you only have a business key (amasterId, a display name),searchthe entity bycontentDefinitionId Equalsplus that key and read the matching item'sid— thatidis thecontentId(seerecipes/search-patterns.md). masterIdgroups language variants. A record localized into several languages shares onemasterId; each variant has its ownid/languageId. Passlanguage_idto target a specific variant (orNone/nullfor the default).- Append safely. Because list fields are truncated to the length you send, the
add_to_collectionpattern (read current → concat → write) is the safe way to grow any multi-valued field (collections,tags,relatedContent, …). - Tear down what you create. Records made with
create_content/create_then_populateare Class C side effects — delete them withdelete_content(thedelete_recordhelper above) when done. Theassetentity is the exception: confine itscontent_idto your run-root anchor and let the run-root teardown reclaim it. - Asset metadata shortcut. For the common asset cases (rename, custom properties,
language, security) prefer the purpose-built
update_asset/update_asset_language/update_asset_securitycomponents — they wrap the same writes with asset-aware, Class A/B confinement. Reach forupdate_contentwhen you need an arbitrary entity field not exposed by those wrappers.
