entities[] reference
An entity is a logical record type (company, contact, deal, …) plus
one or more schemas that describe how that record looks in each system
the connector touches. The HubSpot schema (schemas.hubspot) is by far the
most common.
See connector.md for the surrounding ConnectorConfigV3
envelope, and simple-vs-federated.md for when to
use a single hubspot schema versus a multi-schema federated layout.
Shape at a glance
A minimal simple-mode entity (1 source, HubSpot target):
{
"name": "company",
"schemas": {
"hubspot": {
"object_type": "companies",
"identity": {"strategy": "match_or_create", "property": "hs_tax_id"},
"identity_source_path": "$.payload.id",
"identity_response_path": "$.id",
"field_mappings": [
{"source": "$.payload.name", "target": "name"},
{"source": "$.payload.vat_number", "target": "hs_tax_id"}
]
}
}
}A federated entity adds canonical and source-specific schemas alongside
hubspot; see simple-vs-federated.md.
EntityV3 fields
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Unique within the config. Used by flow steps as entity. |
schemas | dict[str, EntitySchemaSpec] | yes | Keyed by system name (hubspot, canonical, <source>). |
The schemas dict accepts arbitrary keys, but two are behaviorally special:
hubspot- recognized by HubSpot-targeting actions likefind/upsert(the runtime raisesPermanentErroriffindis invoked withtarget != "hubspot").canonical- the conventional intermediate shape thattransformsteps write into (see simple-vs-federated.md).
Other keys are used for non-HubSpot targets via the post action.
Unlike EntitySchemaSpec, IdentityConfig, and FieldMapping, EntityV3
does not declare extra="allow". Passing unknown fields (for example a
literal mode key) at the entity level will fail validation.
EntitySchemaSpec fields
| Field | Type | Required | Notes |
|---|---|---|---|
object_type | string or null | no | The target system’s object identifier (e.g., "companies", "0-2"). |
identity | IdentityConfig or null | no | How to identify existing records. See “Identity” below. |
identity_source_path | string (JSONPath) | no | JSONPath into the inbound payload that yields the external id. |
identity_response_path | string (JSONPath) | no | JSONPath into the target’s response that yields the new external id. |
credential_name | string or null | no | Name of the credential to use for this schema. Optional - outbound schemas can run without auth. If declared, the publish-time validator checks the credential exists in your org. |
field_mappings | list[FieldMapping] | no | Defaults to []. See “Field mappings” below. |
EntitySchemaSpec declares extra="allow"; unknown keys do not raise but
are ignored by the runtime.
Identity
{"identity": {"strategy": "match_or_create", "property": "hs_tax_id"}}IdentityConfig.strategy is one of three values (verified against
(internal plugsync tooling - see the source repo)):
| Strategy | Behavior |
|---|---|
match_or_create | Look up the local mapping first. If absent and the search property is set AND present in the payload, search the target by that property. Reuse on hit, otherwise create. |
external_id_only | Look up the local mapping only. If a mapping exists, reuse it; otherwise the resolver returns create with no remote search. |
by_email | Search the target directly by an email property (defaults to email) drawn from the inbound payload. Unlike match_or_create and external_id_only, by_email does not check the local mapping. On miss the resolver returns action=not_found rather than action=create. |
IdentityConfig declares extra="allow", so per-strategy parameters
(for example property for match_or_create) ride along on the same object.
The resolver reads the search property out-of-band, so this page does not
prescribe an exhaustive list; consult the engine source for the live set.
Publish-time identity validator
When an entity schema declares an identity config and the schema key is
not hubspot, the publish validator (_validate_identity_config in
(internal plugsync tooling - see the source repo)) requires identity_response_path
to be set. Without it the runtime cannot writeback the mapping after a
successful create, so the next event for the same record would duplicate.
Missing the path blocks publish with a ValidationFailedError (HTTP 422),
not a draft-save error.
Field mappings
{"source": "$.payload.vat_number", "target": "hs_tax_id"}{"source": "$.payload.created_at", "target": "created_at", "transform": "iso_to_unix_ms"}FieldMapping fields:
| Field | Type | Required | Notes |
|---|---|---|---|
source | string (JSONPath) | yes | Read path. Common roots: $.payload.*, $.canonical.*. |
target | string | yes | Target property name in the schema’s object_type. |
transform | string or null | no | Name of a registered transform. See “Transforms” below. |
args | dict or null | no | Arguments passed to the named transform. |
FieldMapping declares extra="allow"; unknown keys are ignored.
JSONPath conventions
The field-mapping resolver (_resolve_value in
(internal plugsync tooling - see the source repo)) recognizes exactly these roots:
$.payload.*- the raw inbound event body (inbound flows).$.canonical.*- the intermediate canonical shape (federated mode).$<step_id>(no dot) - the full output of a prior step in the same flow.$.<step_id>.*- a nested field on a prior step’s output.
Anything else (for example $.identity.* or $.event) returns None -
those are not valid field-mapping roots.
Transforms
The transform registry comes from the TransformNode union in
(internal plugsync tooling - see the source repo). Four variants are supported:
| Variant | Purpose |
|---|---|
TransformNodePath | Read a JSONPath, optionally with default and transform / args. |
TransformNodeTemplate | String template ("{first} {last}"), with safe-brace validation. |
TransformNodeConst | Inline constant value. |
TransformNodeConcat | Concatenate a list of values. |
Named transforms (the strings passed in the mapping’s transform field) are
specific to each project; consult the engine source or your connector’s
configured transforms to enumerate the live set.
The publish-time transform validator (_validate_transforms in
publish_service.py) requires the transform name on every FieldMapping
to be either (a) a name in the engine’s built-in transform registry
(supported_transform_names()), or (b) a name declared under
config.transforms (the config-level transform registry, see roadmap).
Unknown names fail at publish with ValidationFailedError, not at draft save.
See flows.md for how field_mappings are consumed inside
upsert / create / update steps, and how transform steps populate
$.canonical.* from $.payload.* in federated mode.