How to use
- Copy the installation command and review the source package before installing it in your own agent environment.
- Check the source document for required tools, credentials and supported hosts.
- Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/port-labs/port-skills --skill port-workflowsBefore you start
- Installation does not include a model subscription, API allowance or permission to access your files.
- Repository stars and installation counts describe activity, not an independent quality or security test.
Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
Port workflows
A workflow is Port's visual, node-based automation layer. Nodes (triggers, actions, conditions, inputs) connect into a graph; running the workflow walks that graph, passing each node's output to the next as JQ-templated data.
Use this skill when
Use this skill to author or edit workflow JSON: adding a self-service or event trigger, adding an action node (webhook, upsert entity, an integration action, Kafka, Cursor Agent, or AI), branching with a condition node, gating on human approval with an input node, or wiring the connections and JQ templates between them. This skill is reference-only: the JSON it produces is valid without a live Port account, though applying it needs API credentials (see Prerequisites).
Out of scope: Port's older, single-step Actions & Automations model (not covered by a skill in this repo yet; see the comparison table for when to use which), blueprint schema design (port-blueprints), and CI/CD pipeline files such as .github/workflows/*.yml, GitHub Actions is a separate product that a workflow can trigger (via INTEGRATION_ACTION) but does not replace.
Prerequisites
- Go over the
port-getting-startedskill first if this is your first time
working with Port.
- To create or update workflows without MCP: a Port
CLIENT_IDand
CLIENT_SECRET (... menu > Credentials in the Port app), exchanged for a bearer token at POST https://api.port.io/v1/auth/access_token.
- If Port's MCP server is connected, this skill can use its workflow tools
(list_workflows, get_workflow, upsert_workflow) to read or apply a workflow directly instead of you copying JSON in by hand, but the raw API calls in Step 6 always work as a fallback. Search search_port_knowledge_sources for anything this skill doesn't cover.
- Workflows are in open beta: cross-check unfamiliar fields against
docs.port.io/workflows before shipping.
Step 1 - Sketch the graph and pick trigger type(s)
Precondition: you know what should start the automation and what it should do. Action: decide SELF_SERVE_TRIGGER (a user submits a form on demand), EVENT_TRIGGER (an entity is created, updated, deleted, or a timer property expires), or both, a workflow can define multiple trigger nodes feeding the same downstream graph. Then list the actions in order: webhook calls, entity upserts, integration dispatches, AI steps, with any branching (condition) or human approval (input) points. Fallback: if the trigger is unclear, default to a self-service trigger, it is the safer, opt-in default and easiest to test.
Step 2 - Configure the trigger node(s)
Precondition: trigger type chosen. Action: for SELF_SERVE_TRIGGER, define userInputs.properties (and required); for EVENT_TRIGGER, define event.type and blueprintIdentifier, plus an optional JQ condition to filter which events fire the workflow. Full field reference, including contexts (bolt-menu and create-entity surfacing) and variant, is in references/node-types.md. Fallback: never add title, icon, or description to a trigger node, those fields are ignored there and belong on action nodes instead.
Step 3 - Add action and flow nodes
Precondition: trigger is defined. Action: add one node per step. See references/node-types.md for the full field reference and JSON shape of every node type: WEBHOOK, UPSERT_ENTITY, INTEGRATION_ACTION (GitHub, Claude managed agents, and others), KAFKA, CURSOR_AGENT, AI_AGENT, AI, CONDITION, INPUT. Reference upstream data with JQ templates, see references/data-flow.md. Fallback: if a step just needs to call an HTTP API, prefer WEBHOOK over building a custom integration, and prefer UPSERT_ENTITY over a raw WEBHOOK call to Port's entity API, it is simpler and less error-prone.
Step 4 - Wire connections
Precondition: nodes exist. Action: add a connections entry (sourceIdentifier, targetIdentifier) per edge. CONDITION and INPUT nodes need sourceOutletIdentifier (or fallback: true) on every outgoing connection, matching one of the node's outlets. Trigger nodes can only be sources, never targets. Each node (and each outlet) can have at most one outgoing connection, fan-out is not supported, chain nodes sequentially instead. Fallback: got a fan-out requirement (one node feeding two independent next steps)? Split it into two workflows, or serialize the two branches behind a single downstream node instead of trying to fan out directly.
Step 5 - Set self-service permissions
Precondition: workflow has a SELF_SERVE_TRIGGER node. Action: set permissions on the trigger node's config. Omitted or {} means Admin only. See references/permissions.md for static (roles/users/teams) and dynamic policy rules (based on user, team, or form-input properties). Fallback: event triggers have no permissions field, event-driven workflows run under the organization's automation identity, not a specific user.
Step 6 - Create or update via the Port API
Precondition: you have a bearer token and a complete workflow JSON body (identifier, nodes, connections, at minimum).
Action: use the endpoints below (workflow_identifier is the workflow's identifier).
| Operation | Method | Path |
|---|---|---|
| Create | POST | /v1/workflows |
| List | GET | /v1/workflows |
| Get | GET | /v1/workflows/{workflow_identifier} |
| Update (creates a new version) | PUT | /v1/workflows/{workflow_identifier} |
| Delete | DELETE | /v1/workflows/{workflow_identifier} |
| Trigger a run | POST | /v1/workflows/{workflow_identifier}/runs |
curl -L -X POST 'https://api.port.io/v1/workflows' \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H 'Content-Type: application/json' \
--data @workflow.jsonFallback: a 409 on create means the identifier is taken, PUT an update instead. A 422 usually means a connections entry references a node identifier that doesn't exist, or a CONDITION/INPUT connection is missing sourceOutletIdentifier, check Common pitfalls below.
Complete examples
assets/self-service-deploy-workflow.json - self-service trigger, environment branching with a CONDITION node, and a Slack notification.
assets/event-driven-ai-incident-response.json - event trigger with a JQ filter, an AI node with outputSchema for structured output, and an entity upsert.
assets/approval-workflow.json - self-service trigger gated by an INPUT approval step before a production deploy.
Common pitfalls
| Symptom | Cause | Fix | ||
|---|---|---|---|---|
.outputs.my-node.field silently returns nothing | Hyphens in a node identifier break JQ dot notation (- reads as subtraction) | Use bracket notation .outputs["my-node"].field, or keep node identifiers snake_case so dot notation works | ||
Adding variables makes existing output references break | variables replaces a node's entire default output, response and friends disappear | Re-declare anything downstream still needs inside variables, e.g. "response": "{{ .result.response }}" | ||
CONDITION/INPUT connection has no effect, or workflow rejects it | Missing sourceOutletIdentifier (or fallback: true) on the connection | Every outgoing connection from a CONDITION or INPUT node must set sourceOutletIdentifier matching one of the node's outlets, or fallback: true | ||
UPSERT_ENTITY (or another structured node) after an AI node gets nothing | The AI node emitted free-form text, not JSON | Set outputSchema (JSON Schema, type: "object") on the AI node, then parse with `{{ .outputs.node_id.response \ | fromjson \ | .field }}` |
Event trigger fires but .outputs.trigger.diff.before is unexpectedly null | ENTITY_CREATED has no before, ENTITY_DELETED has no after | Branch on .outputs.trigger.action (CREATE/UPDATE/DELETE) before reading diff.before/diff.after | ||
| Workflow silently does nothing when a node fails mid-graph | Default onFailure is "terminate", the whole run stops | Set "onFailure": "continue" on WEBHOOK, UPSERT_ENTITY, KAFKA, or INTEGRATION_ACTION nodes that shouldn't block the rest of the run | ||
| GitHub Actions workflow never fires | Used WEBHOOK to call the GitHub API by hand, or used the wrong field name for status reporting | Use INTEGRATION_ACTION with integrationProvider: "github-ocean", and reportWorkflowStatus (not reportStatus) to get status back in Port |
Quick reference
- Node types:
SELF_SERVE_TRIGGER,EVENT_TRIGGER(triggers);WEBHOOK,
UPSERT_ENTITY, INTEGRATION_ACTION, KAFKA, CURSOR_AGENT, AI_AGENT, AI (actions); CONDITION, INPUT (flow control). Full reference: references/node-types.md.
- Data access:
{{ .outputs["node_id"].field }}(previous node output),
{{ .outputs.trigger.field }} (always aliases the trigger that fired), {{ .secrets["name"] }}, {{ .workflowRun.trigger.by.email }}. Full reference: references/data-flow.md.
- Permissions:
{}/omitted = Admin only;roles/users/teams= static OR
match; policy = dynamic rules over user, userTeams, or form context. Full reference: references/permissions.md.
- Auth:
POST /v1/auth/access_tokenwithclientId/clientSecretfor a
bearer token; no Authorization header needed when a workflow node itself calls https://api.port.io.
- Workflow top-level fields:
identifier(required, ≤60 chars,
^[A-Za-z0-9@_:-]+$), title, icon, description, category (groups it in the UI), allowAnyoneToViewRuns (default true), nodes, connections.


