Read the records a reference attribute links to, load their own attributes with export.load_custom_entities, and look custom entity records up by any value.
A reference attribute links a product to records of a custom entity — its certificates, its manufacturer, its care instructions. The attribute itself carries only each linked record’s id, name and identifier; to read anything else you need export.load_custom_entities, or export.custom_entity for lookups that are not driven by a reference. This page covers all three.
The reference attribute
Section titled “The reference attribute”When it has links, it holds a list, even if there is only one.
Each reference carries the id, name and identifier of the linked record — but none of its other values:
| Property | Description |
|---|---|
target_id |
Internal id of the linked record |
target_name |
Name of the linked record, from the custom entity’s label attribute |
target_identifier |
The linked record’s identifier |
{{ for m in record.manufacturer }} <manufacturer>{{ m.target_name }}</manufacturer>{{ end }}Reading every linked record — export.load_custom_entities
Section titled “Reading every linked record — export.load_custom_entities”To read a linked record’s own attributes — not just its id, name and identifier — load it with export.load_custom_entities, naming the record (or a variant) and the reference attribute’s code:
{{ for cert in export.load_custom_entities(record, 'certificates') }} <certificate id="{{ cert._id.entityid }}"> <name>{{ cert._meta.name }}</name> <number>{{ cert.certificate_number }}</number> <valid_until>{{ cert.valid_until | date.to_string '%Y-%m-%d' }}</valid_until> </certificate>{{ end }}This returns every linked record, in the order they were linked on the product — one call, whatever the attribute’s cardinality. An attribute with no links returns an empty list, so the loop needs no separate guard. Duplicates (the same record linked twice) are kept, not collapsed.
The variant form works the same way, for a reference attribute defined on variants rather than the product:
{{ for v in variants }} {{ for c in export.load_custom_entities(v, 'care_instructions') }}<care>{{ c.text }}</care>{{ end }}{{ end }}On a loaded record:
| Property | Description |
|---|---|
cert.my_attribute_code |
Value of that attribute |
cert._meta.name |
Name of the record |
cert._meta.identifier |
Identifier of the record |
cert._id.entityid |
Internal id of the record |
_meta.name and _meta.identifier always match the reference’s own target_name and target_identifier, so a loop over export.load_custom_entities does not need to carry the reference alongside it.
A wrong attribute code, an attribute that is not a reference attribute, or a reference to something other than a custom entity (a product reference, for example) is reported in the job log — see background jobs.
Reading one linked record you already have a reference for
Section titled “Reading one linked record you already have a reference for”If a template loops record.<attribute> itself — to read target_name, or to keep the link order alongside other work — export.custom_entity(key).get accepts the reference directly, instead of picking target_id and the lookup field apart by hand:
{{ for r in record.certificates }} {{ cert = export.custom_entity('certificates').get(r) }} {{ if cert }} <certificate number="{{ cert.certificate_number }}">{{ r.target_name }}</certificate> {{ end }}{{ end }}get(r) is equivalent to get('id.entityid', r.target_id), and it exists so a template never has to know that. The long form is a trap: id.entityid pairs with r.target_id, meta.identifier with r.target_identifier.value — and mixing them up, or dropping the .value, silently finds nothing.
Looking up by any text attribute
Section titled “Looking up by any text attribute”For a lookup that is not reference-driven — every manufacturer in Germany, say, rather than the one(s) a product links to — use export.custom_entity(key).get or .find with any attribute code, passing the custom entity’s key from Settings → Custom entities:
{{ for m in export.custom_entity('manufacturer').find('country_code', 'DE') }} {{ m._meta.name }}{{ end }}get returns the first match, find returns all of them.
export.custom_entity — reference
Section titled “export.custom_entity — reference”.get attribute, value |
The first matching record. Also accepts a reference directly: .get(r). |
.find attribute, value, limit |
All matching records, optionally capped. |
.key |
The custom entity’s key, as stored. |
For both, the value must match exactly — case sensitive, no partial matching.
The old <attribute>.data.<attribute> fields
Section titled “The old <attribute>.data.<attribute> fields”Older templates may read a linked record through keys like record.manufacturer.data.support_url. Those keys are gone; replace them with export.load_custom_entities (above).
What to read next
Section titled “What to read next”- The data in a template —
record,variants, and plain attributes - Template function reference — everything callable
- Testing and debugging templates — reading the errors a lookup can raise

