Plugin runtime (Lambda + TypeScript)
A plugin is the imperative escape hatch for step logic the Event Flow Manifest can’t express: an HTTP call to a third-party system, stateful per-record logic (idempotency, dedup), or branching too complex for the when DSL. Everything else — field mapping, identity resolution, HubSpot reads/writes, associations — stays in the declarative manifest. That split is enforced, not a convention: plugins never write to HubSpot directly, they return data that a core step (update_by_id, upsert, …) writes.
A plugin runs as an AWS Lambda function written in TypeScript/JavaScript. plugsync stores the source and the bundle you push and treats that stored copy as the authoritative one — there is no @plugsync/sdk package to install, on npm or anywhere else, and nothing you have to keep in a git repository for plugsync to find. You author, push, test, and promote a plugin entirely through the plugsync CLI and the plugin API; you never need AWS credentials.
Mental model
flow step (action: "plugin")
|
v
plugsync builds a PluginPayload from the live execution context,
stripping every credential and live client object
|
v
AWS Lambda invoke (function_arn + version, resolved and embedded at publish time)
|
v
your handler(event) runs, returns a PluginResponse (plain JSON object)
|
v
plugsync maps action (continue/skip/retry) and writes the response into
ctx.outputs[step.id] for later steps to read via `$<step_id>.<field>`The step that invokes your plugin looks like this in an authored draft:
{"id": "generate_doc", "action": "plugin",
"plugin": {"logical_name": "acme_doc_generator",
"config": {"store_keys": ["$.payload.object.id"]}}}You author only logical_name (the plugin’s name in your org) and an optional config object of static settings. At publish time plugsync resolves the plugin’s Lambda coordinates and rewrites the step’s plugin block into {logical_name, function_arn, version, capabilities, config} — that resolved block, not the authored one, is what the published manifest carries and what runs. See Promotion and version pinning.
Lambda is the only plugin runtime. A
pluginstep always names a plugin bylogical_name; there are nomodule/functionfields to set on it.
When do you need a plugin, and which tier enables it
When you need one: only for step logic the Event Flow Manifest genuinely cannot express — an HTTP call to a third-party system, stateful per-record logic (idempotency, dedup, a running counter), or branching too complex for the when DSL. Field mapping, identity resolution, conflict handling, HubSpot reads/writes, and associations are all declarative and never need a plugin; reach for one only after confirming the manifest can’t do it.
Which tier enables it: the Plugin System requires a tier that includes it — today that is Enterprise and Custom-Dedicated; Developer, Starter, Pro, Scale, and the reverse trial do not have it. Three plugin API endpoints are gated: POST /api/plugins, POST /{id}/push and POST /{id}/promote return 402 Payment Required with error: plugin_system_not_allowed for a tier without it. The others (list, read, update, delete, invoke, logs, metrics, lifecycle hooks) are not gated. plugsync plugin push surfaces that 402 with your current tier and an upgrade link rather than a raw error; plugin init only scaffolds files locally and never calls the API. See Tier gate for the enforcement details and Plan limits for what each plan includes.
If you are authoring a draft and already know you’ll need a plugin step before your org has the tier, the preflight check on your connector’s draft (GET /api/connectors/{id}/preflight, surfaced in the dashboard’s Review-changes drawer) flags it with a plugin_step_tier_not_allowed warning as soon as the step is saved — well before you get to plugin push and hit the 402 there.
Trust model, briefly
A plugin is not sandboxed against malicious code. The security boundary is: a capability allow-list declared on the plugin (Capabilities), a best-effort static linter enforced at push time, explicit promotion (live connectors only run a build someone promoted to live; staging is an optional intermediate step, not a mandatory gate), and the contractual trust of the org’s tier. HubSpot tokens and the full credential map never cross into the plugin payload; the one exception is a source explicitly granted through the auth capability, whose resolved headers (secret included) do. See PluginPayload / PluginResponse for exactly what is and isn’t included.
Where to read next
| Page | What is there |
|---|---|
| payload-contract.md | Every field of PluginPayload and PluginResponse, and what is deliberately stripped. |
| capabilities.md | The http / store / auth capability allow-list: format, where it’s set, real examples. |
| dev-loop.md | plugsync plugin init / push / invoke / logs, no AWS credentials needed, test connectors and $LATEST. |
| promotion.md | The draft -> dev -> staging -> live state machine, version pinning, and how ARN+version get embedded in a published config. |
| lifecycle-hooks.md | The setup / teardown hooks: their payload (not a PluginPayload), how a handler tells a hook from an event, and what an exception does. |
| generate-doc-walkthrough.md | A complete worked example, end to end: the acme connector’s acme_doc_generator. |
Prerequisites
- Your org’s tier includes the Plugin System (Enterprise and above as of this writing; creating, pushing and promoting a plugin return 402 otherwise). See capabilities.md and Plan limits.
plugsyncCLI installed (pip install plugsync-clifrom PyPI), thenplugsync auth login. The CLI points athttps://app.plugsync.comby default.- Node.js (any LTS >= 18) and
esbuildonPATH(npm install -g esbuild) — the CLI bundles your TypeScript by shelling out to it.
Out of scope
This guide deliberately does not cover:
- A TypeScript SDK package. There is no
@plugsync/sdkto install; the payload and response shapes on payload-contract.md are plain interfaces you paste into your own handler. - A
hubspot:*capability broker letting a plugin call HubSpot directly. It is a reserved namespace, deferred and not implemented — see Reserved, not implemented. - A dashboard UI for plugins. Everything here is CLI/API-driven.