Skip to Content
ReferenceSchema provisioningOverview

HubSpot schema provisioning

plugsync provisions the HubSpot schema your connector config references: custom properties, property groups, and custom object schemas are created on your portal automatically. You do not pre-create anything in HubSpot, with three explicit exceptions listed in What is never created below.

Provisioning runs through four mechanisms, all backed by the same idempotent engine ((internal plugsync tooling - see the source repo)) — creating something that already exists is always a no-op:

MechanismWhen it runsFailure behavior
PublishEvery publish, synchronouslyPublish fails with an actionable 422; nothing goes live
Portal connect / rebindBinding a connector to a (new) portalBest-effort: logged, never blocks the bind
One-click propertyOn demand from the mapping editor422 with the same actionable message
Enum option auto-appendAt runtime, before each HubSpot writeBest-effort: logged, the write proceeds

1. At publish (synchronous)

When you publish a connector config, plugsync walks every entity that has a schemas.hubspot block with an object_type and creates whatever is missing on the bound portal, before the new version is persisted (_provision_hubspot_schemas_sync in (internal plugsync tooling - see the source repo)):

  • Property groups — created properties are placed in a dedicated group so they are visible as “created by plugsync” and enumerable for a later cleanup. The group defaults to plugsync (label Plugsync) and is configurable per connector — see Property group and prefix. An entity’s schemas.hubspot.property_group still overrides the group for that entity.
  • Custom properties — see Which properties get provisioned.
  • Custom object schemas — an object_type of the fully-qualified custom form p<portalId>_<name> that does not exist on the portal yet is created on the fly, with the config’s custom properties embedded in the schema (first custom property becomes the primary display property). This requires the crm.schemas.custom.write scope — see Custom object schemas and the schema scope.

Provisioning at publish is atomic from your point of view: any failure surfaces as a 422 on the publish call with one structured, user-actionable error per failing entity ({entity, object_type, error}), and no new version is created — your draft is untouched, fix the cause and publish again. Raw HubSpot transport errors never leak into the message.

Which properties get provisioned

For each entity with a schemas.hubspot block, the property list is derived from the config (_collect_required_hubspot_properties):

Config elementProvisioned as
identity.property (when the identity strategy uses a property)A string / text property with that name
Each field_mappings[].targetA property named target
A mapping with target_template instead of targetEvery name listed in its provision_names (issue #519). A template without provision_names is invisible to provisioning — declare the names it can expand to

Type and appearance default to HubSpot type string with field type text; a mapping can override them per-property with hubspot_type, hubspot_field_type, and hubspot_label. Targets that are JSONPath expressions ($.canonical.* and friends) are canonical-side mappings, not HubSpot writes, and are skipped.

Provisioning is idempotent: properties that already exist on the portal are left exactly as they are (their type, label, and options are never modified).

Property group and prefix

The properties plugsync creates are made transparent and enumerable by two knobs, set once per connector under settings.hubspot_provisioning:

{ "settings": { "hubspot_provisioning": { "property_group": "plugsync", "property_group_label": "Plugsync", "property_prefix": "plugsync_" } } }
KnobDefaultMeaning
property_groupplugsyncInternal name of the dedicated group the created properties live under
property_group_labelPlugsyncThe group’s display label in HubSpot
property_prefixplugsync_Naming convention for the custom properties plugsync manages; together with the group it makes the managed set derivable

Every self-service connector gets the defaults with no configuration. The knobs exist mainly for enterprise / Custom-Dedicated connectors that want their own naming.

Both knobs can be changed freely — change property_group any time and the next publish creates / uses the new group; change property_prefix any time too, and the publish goes through. The prefix is a reserved naming convention: plugsync does not rewrite the internal names it provisions, so editing it never orphans an existing property and never creates a duplicate. It documents which names plugsync considers its own (decision on issue #1190, option B) and feeds the primitive a future uninstall would use to enumerate the managed set.

Note: the prefix is a convention applied by naming, not a silent rewrite of your field_mappings[].target names. The flow runtime writes each mapping’s target to HubSpot verbatim, so a property’s internal name is whatever the config author writes; plugsync groups those properties, keeps the prefix stable, and never rewrites a target behind your back.

Pipelines are not auto-created

Publish provisioning covers properties, groups, and custom object schemas. It does not create pipelines or pipeline stages: a pipeline or stage ID your config references must already exist on the portal. The dashboard’s mapping editor discovers the portal’s pipelines and stages (GET /api/connectors/{connector_id}/hubspot-pipelines/{object_type}) so you can pick valid IDs instead of typing them.

2. At portal connect / rebind (best-effort)

Binding a connector to a portal re-runs provisioning of the active published config against that portal (reprovision_active_hubspot_schemas, PR #306). This covers:

  • the HubSpot OAuth callback (connecting a portal),
  • re-binding a connector to a different credential (PATCH of the connector’s credential_id),
  • the AI agent’s bind_portal tool.

Connect a brand-new portal to an already-published connector and its custom properties materialize there without a re-publish. Differences from the publish-time path:

  • Best-effort: failures are logged and swallowed — a connect/rebind is never broken by a HubSpot error. The publish-time path is the strict one.
  • No-op without an active version: a connector that has never been published has nothing to provision yet; the first publish handles it.
  • Idempotent: re-binding an already-provisioned portal is a cheap no-op.

3. One-click property from the mapping editor

A source field with no matching HubSpot property can be provisioned without leaving the dashboard (issue #89): the mapping editor’s property picker offers to create it on the spot. The underlying endpoint:

POST /api/connectors/{connector_id}/hubspot-properties/{object_type}
Body fieldRequiredDefaultNotes
nameyesHubSpot internal property name
labelnoTitle Case of nameDisplay label
typenostringHubSpot property type
fieldTypenotextHubSpot field type
group_namenoplugsyncProperty group (created if absent)

Returns 201 with {"name": ..., "created": true} — or created: false when the property already existed (idempotent no-op). Failures return the same actionable 422 messages as publish. This is the same SchemaProvisioner the publish and connect/rebind paths use, not a second implementation.

4. Enum option auto-append at runtime

When a flow writes a value to a HubSpot enumeration property whose option list does not contain that value yet, the engine appends the option (one HubSpot Schema API PATCH) before writing ((internal plugsync tooling - see the source repo), issue #191). No config field enables it; it is keyed on the target property being an enumeration. It is tightly gated:

  • enumeration-type properties only;
  • never on read-only, calculated, or externally-managed (externalOptions, e.g. owner fields) properties;
  • additive only: existing options are never removed or reordered;
  • best-effort: a failed append (e.g. a missing schema scope) is logged and the value is written as-is, exactly as without the feature.

Full behavior, gates, and the propagation caveat are documented in the Config V3 entities reference.

What is never created

Three limits are by design. Everything else your config references on the HubSpot side is provisioned for you.

Forms

plugsync never creates HubSpot forms. The submit_form step action writes submissions to an existing form, addressed by its form_guid, via the Forms v3 secure submissions API (submit_form in (internal plugsync tooling - see the source repo), issue #489). Form creation would require marketing scopes the plugsync app deliberately does not request. Create the form in HubSpot and reference its GUID.

Reserved property prefixes: hs_* and a<appId>_*

HubSpot reserves two property-name prefixes that its create API rejects outright:

  • hs_* — HubSpot-managed properties (e.g. hs_price_eur);
  • a<appId>_* — integrator-managed properties owned by another app (e.g. a38567592_synced_at, issue #418).

Mapping to one of these is fine when the property exists on the portal: provisioning treats reserved-prefix targets as prerequisites, verifies them, and never attempts to create them. If one is missing, publish fails with an error naming the property and, where known, the portal feature that materializes it — e.g. a missing hs_price_eur means multicurrency for EUR is not enabled on the portal. Enable the missing portal feature (or install the owning app), or remove the mapping.

Custom object schemas and the schema scope

Creating a custom object schema (as opposed to properties on an existing one) requires the crm.schemas.custom.write scope on the connector’s HubSpot credential. Two cases:

  • Standard OAuth connection (plugsync public app): HubSpot does not allow public apps to request crm.schemas.custom.write — it is a private-app-only scope. Publishing a config that targets a custom object whose schema does not exist yet fails with an actionable error: create the custom object schema on the portal once (Settings -> Objects -> Custom objects), then publish again. From then on, its properties are provisioned normally (crm.objects.custom.* and crm.schemas.custom.read are part of the standard OAuth scope set).
  • Private app token credential: include crm.schemas.custom.write in the token’s scopes and plugsync creates the schema at publish, custom properties embedded.

Do I need to pre-create anything on the portal?

No, except the three cases above. Recap:

Your config referencesplugsync creates it?
A custom property (any non-reserved name)Yes — at publish, rebind, or one-click
A property groupYes
A custom object schema (p<portalId>_* object type)Yes with a credential granting crm.schemas.custom.write (private app token); otherwise create the schema once on the portal, its properties are then provisioned normally
A new option value on an enumeration propertyYes — appended at runtime, additive
An hs_* or a<appId>_* propertyNever — prerequisite: enable the portal feature / owning app
A pipeline or pipeline stageNo — create it on the portal, pick it from the dashboard
A form (submit_form)Never — create the form, reference its GUID
Last updated on