← Plugin catalog
Developer Tools

Port Agent Skills (EU)

Port v1.0.8

Build and govern in Port

Full description ↓

View on ChatGPT ↗

Save this plugin to Following and return to review observed changes. Follow up to 10 plugins free.

Description

From the marketplace listing

Help platform engineers work with Port: model blueprints and a connected context lake, configure integrations, build workflows and dashboards, manage access, and use Terraform. Includes reusable skills and an optional authenticated Port MCP connection for EU tenants. MCP actions follow the permissions of the connected Port account. Review proposed changes before applying them.

Language: English · Automatically detected from descriptions.

Competitor profile

From the developer’s listing and package; capabilities are not independently tested. Package observed Nov 3, 2026.

Declared capabilities

  • Work with Port blueprints, entities, and context lakes
  • Configure and troubleshoot Port integrations
  • Build Port workflows and dashboards

Declared limitations

  • Refuse bulk destructive deletion of the catalog
  • Refuse access outside the connected Port account
  • Refuse unrelated payment / commerce actions

Review scenarios; not an exhaustive list.

Cost & access

This plugin does not sell products or process payments. It helps users model and operate a Port developer portal using skills and authenticated Port MCP tools scoped to the connected account.

Terms may have changed since this observation.

Starter prompt

Help me design a Port context lake for our services and teams.

View onboarding →

View package source →

Keywords

Files & skills

File archives

Plugin package94 files · 207 KBBrowse files →
Skill instructions
port-blueprints8.76 KB

View saved version →

---
name: port-blueprints
description: "Model a Port context lake data model using blueprints, defining identifiers, titles, icons, properties, relations, and calculation, mirror, or aggregation properties, then author them with the raw Port API. Use when asked to create a Port blueprint, define a Port data model, add a property to a blueprint, relate two blueprints, add a calculation property, add a mirror property, or add an aggregation property."
license: MIT
compatibility: "Claude Code, Cursor, Codex CLI, GitHub Copilot"
metadata:
  version: "1.0.0"
  author: port-labs
  repository: https://github.com/port-labs/port-skills
  tags: port,blueprints,data-model,reference
  summary: Model your context lake with Port blueprints, properties, and relations
---

# Port blueprints

A blueprint is Port's schema primitive. It defines a type of asset in the
context lake (a `service`, a `package`, a `cluster`, anything), the
properties that describe it, and how it relates to other blueprints. Every
entity in Port's context lake is an instance of a blueprint, the same way a
database row is an instance of a table schema.

## Use this skill when

Use this skill to design or edit a Port data model: creating a new blueprint,
adding or typing a property, relating two blueprints, or adding a
calculation, mirror, or aggregation property. It is reference-only, built
from the static blueprint JSON schema and the raw Port API, so it works
without a live Port account or Port MCP server connected. Without
credentials it still produces valid blueprint JSON the user can apply later.

Out of scope: workflows (`port-workflows`),
scorecards (`port-scorecards`), integration mapping (`port-integrations`),
the Terraform/Pulumi provider reference (`port-terraform`, pointed to in
[Step 6](#step-6---create-or-update-the-blueprint-via-the-api)), and deciding
*what* to model and how blueprints should relate at the data-model-design
level (`port-context-lake` owns that; this skill is the JSON mechanics it
points to).

## Prerequisites

- Go over the `port-getting-started` skill first if this is your first time
  working with Port.
- To apply changes without MCP: a `CLIENT_ID`/`CLIENT_SECRET` pair (**...**
  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 it to read the
  current blueprint (`list_blueprints`) and apply the result directly
  (`upsert_blueprint`) instead of you copying JSON in by hand; the raw API
  calls below always work as a fallback. Search
  `search_port_knowledge_sources` for anything this skill doesn't cover.

## Step 1 - Check whether the blueprint already exists

Precondition: you have a bearer token.
Action: `GET /v1/blueprints/<identifier>`. `200` means it exists, edit it
instead of duplicating it. `404` means it's safe to create.
Fallback: no credentials yet. Draft the JSON and tell the user to verify
uniqueness before applying it.

## Step 2 - Decide the blueprint's identity

Precondition: you know what real-world asset this blueprint represents.
Action: pick an `identifier` (unique, ≤100 characters, immutable after
creation), a human-readable `title`, and an `icon` from Port's
[built-in icon set](https://docs.port.io/build-your-software-catalog/customize-integrations/configure-data-model/setup-blueprint/#full-icon-list).
Fallback: if the asset isn't precisely named yet, ask, or default to a
camelCase noun (`microservice`, `cloudResource`) and flag it as a guess.

## Step 3 - Define properties

Precondition: you know what data this asset needs to carry.
Action: for each field, add a typed entry under `schema.properties` and, if
mandatory, its key to `schema.required`. See
[references/property-types.md](references/property-types.md) for the full
type system: `string`, `number`, `boolean`, `array`, `object`, string
formats (`url`, `email`, `user`, `team`, `date-time`, `timer`, `yaml`,
`markdown`), `enum`, and meta-properties.
Fallback: if the data actually belongs to a different asset, use a relation
([Step 4](#step-4---add-relations-if-this-asset-connects-to-others)) instead
of duplicating it.

## Step 4 - Add relations if this asset connects to others

Precondition: entities of this blueprint need to reference entities of
another blueprint (ownership, dependency, hierarchy).
Action: add a `relations` entry pointing at the target blueprint's
identifier. See [references/relations.md](references/relations.md) for the
single vs. many distinction and the `required`/`many` constraint.
Fallback: if the target blueprint doesn't exist yet, create it first,
relations require an existing target.

## Step 5 - Add computed properties if useful

Precondition: a value can be derived from this blueprint's own properties, or
from related entities, instead of being ingested directly.
Action: use a calculation property (JQ over existing properties), a mirror
property (pull a value from a related entity), or an aggregation property
(count/sum/average across related entities). See
[references/calculation-properties.md](references/calculation-properties.md).
Fallback: if the source property doesn't exist yet, add it first.

## Step 6 - Create or update the blueprint via the API

Precondition: you have a bearer token and a complete blueprint JSON body.
Action: use the endpoints below. Prefer `PATCH` for additive changes to avoid
clobbering unrelated fields.

| Operation | Method | Path |
|---|---|---|
| Create | `POST` | `/v1/blueprints` |
| Fully replace | `PUT` | `/v1/blueprints/{identifier}` |
| Update (additive) | `PATCH` | `/v1/blueprints/{identifier}` |
| Read | `GET` | `/v1/blueprints/{identifier}` |
| Delete | `DELETE` | `/v1/blueprints/{identifier}` |

```bash
curl -L -X POST 'https://api.port.io/v1/blueprints' \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H 'Content-Type: application/json' \
  --data @blueprint.json
 ```

Fallback: a `422` usually means `required` lists a property that isn't in
`properties`, or a relation `target` that doesn't exist yet. Fix the JSON and
retry, don't silently drop the failing field.

If the user's Port account is managed with Terraform or Pulumi instead of raw
API calls, use the `port-terraform` skill, it owns that reference in depth
and the blueprint JSON shape maps directly onto it.

## Examples

Ready-to-apply templates:

- [assets/basic-service-blueprint.json](assets/basic-service-blueprint.json): a single blueprint, a valid `POST /v1/blueprints` body, with primitive and formatted string properties.
- [assets/blueprint-with-relations.json](assets/blueprint-with-relations.json): a JSON array of two blueprints (`team`, then `microservice`), `POST` them in order, showing a required single relation and a self-referencing many relation.
- [assets/blueprint-with-calculation-property.json](assets/blueprint-with-calculation-property.json): a blueprint with a calculation property, a mirror property, and an aggregation property. Assumes a `kubernetesCluster` and an `incident` blueprint already exist as relation/aggregation targets.

## Common pitfalls

| Symptom | Cause | Fix |
|---|---|---|
| `422` on create/update | `required` references a missing property, or a relation `target` doesn't exist | Create the target first; keep `required` in sync with `properties` |
| Property type change is rejected or corrupts data | Property types are immutable after creation | Create a new property with the right type, [migrate data](https://docs.port.io/build-your-software-catalog/customize-integrations/configure-data-model/migrate-data/), delete the old one |
| Relation rejected with `many` and `required` both `true` | Port forbids that combination | A required relation must be single (`many: false`) |
| Calculation property returns `null` | JQ references a missing path, or a relation title (`.relations.x.title`) under persistent calculation | Test the JQ against a real entity payload; use a mirror property for relation titles |
| New property invisible in the UI table | New properties are hidden by default in table views | Toggle it on with **Manage properties**; this doesn't affect the API or ingested data |

## Quick reference

- Blueprint top-level keys: `identifier`, `title`, `description`, `icon`,
  `schema` (`properties`, `required`), `calculationProperties`,
  `mirrorProperties`, `relations`, `ownership`.
- Property types: `string`, `number`, `boolean`, `array`, `object`, plus
  string formats. Full list: [references/property-types.md](references/property-types.md).
- Relation shape: `{ "title", "target", "required", "many" }`. Details:
  [references/relations.md](references/relations.md).
- Derived properties: calculation (JQ), mirror (path into a relation),
  aggregation (count/sum/average/min/max/median). Details:
  [references/calculation-properties.md](references/calculation-properties.md).
- Auth: `POST /v1/auth/access_token` with `clientId`/`clientSecret`.

Referenced files: 6 — file references are metadata, not package contents.

port-context-lake6.05 KB

View saved version →

---
name: port-context-lake
description: "Design and build a Port context lake: a connected, semantically-rich data model of blueprints, relations, and mirror/aggregation properties that AI agents, scorecards, and workflows can query and act on, not just a plain software catalog. Use when asked to 'build a context lake', 'design a Port data model for AI agents', 'set up the engineering intelligence data model', 'connect services to teams and orgs in Port', 'model our org hierarchy in Port', or 'why can't Port AI answer questions about our org structure'."
license: MIT
compatibility: "Claude Code, Cursor, Codex CLI, GitHub Copilot"
metadata:
  version: "1.0.0"
  author: port-labs
  repository: https://github.com/port-labs/port-skills
  tags: port,context-lake,data-model,ontology,reference
  summary: Design a Port context lake with connected blueprints and semantic relations
---

# Build a context lake

A context lake is Port's data model once it stops being a flat catalog and
becomes a connected graph: blueprints related to each other, with mirror and
aggregation properties, descriptions, and relation titles precise enough
that an AI agent can traverse it and act, not just browse it. This skill
designs that model. For the JSON mechanics of any single blueprint,
property, or relation, use `port-blueprints`; this skill is the layer above
that: what to build, in what order, and why.

## Prerequisites

- Go over the `port-getting-started` skill first if this is your first time
  working with Port, it covers signing up and connecting Port's MCP server.
- If Port's MCP server is connected, this skill can use it to check what
  already exists (`list_blueprints`) and apply the resulting blueprint,
  relation, and property changes directly (`upsert_blueprint`), rather than
  just handing you JSON to apply yourself. Search
  `search_port_knowledge_sources` for anything this skill doesn't cover.

## How to build it

1. **Start from what Port already gives you.** Every account ships built-in
   blueprints (`_user`, `_team`, `_scorecard`, and others) that you extend,
   not replace. See [references/default-blueprints.md](references/default-blueprints.md).
2. **Design the core layer before the provider layer.** Model your
   organization's own shape first (organization, team, service), then relate
   it to provider-specific blueprints your integrations create (GitHub,
   GitLab, Jira, ...), rather than duplicating provider data into your core
   blueprints. See [references/reference-architecture.md](references/reference-architecture.md)
   for a full worked example.
3. **Model hierarchy with a self-relation, not one blueprint per tier.** A
   `parent_team` relation on `_team` handles arbitrary org depth. See
   [references/relations-and-hierarchy.md](references/relations-and-hierarchy.md).
4. **Use ownership instead of a custom team relation.** Port's built-in
   `$team` field already rolls up into `_team` aggregations for free. See
   [references/ownership.md](references/ownership.md).
5. **Add mirror and aggregation properties, not duplicated data.** Mirror a
   single related value onto its source; aggregate across many related
   entities on the blueprint that sits above them. For the JSON shape, see
   `port-blueprints`' [references/calculation-properties.md](../port-blueprints/references/calculation-properties.md);
   for which one to use and where to put it, see
   [references/mirror-vs-aggregation.md](references/mirror-vs-aggregation.md).
6. **Write it as an ontology, not a schema.** Descriptions, relation titles,
   and typed properties are what let an agent understand the graph instead
   of just enumerating it. See [references/ontology.md](references/ontology.md).
7. **Decide integration vs. MCP connector per data source.** Only data that
   needs relations, ownership, or scorecards belongs in the lake as ingested
   entities; everything else can stay a live MCP fetch. See
   [references/ingestion-strategy.md](references/ingestion-strategy.md).

## Examples

- [assets/team-hierarchy-patch.json](assets/team-hierarchy-patch.json): adds
  a `parent_team` self-relation and mirror property to `_team`.
- [assets/service-github-mirrors-patch.json](assets/service-github-mirrors-patch.json):
  relates `service` to `githubRepository` and mirrors its key fields.
- [assets/team-aggregation-patch.json](assets/team-aggregation-patch.json):
  adds an aggregation property rolling up owned services onto `_team`.

## Common pitfalls

| Symptom | Cause | Fix |
|---|---|---|
| Tried to delete or fully redefine `_user`/`_team` | System blueprints can't be deleted, only extended | `PATCH` in new relations only; leave the protected schema alone |
| `service` has no `team` relation and it seems wrong | Port uses built-in ownership (`$team`), not a custom relation, for this | Set `ownership` on `service`, don't add a redundant relation |
| One blueprint per org-hierarchy tier (team, group, division, org) | Hierarchy modeled as a chain of distinct blueprints instead of a self-relation | Use a single `parent_team` self-relation on `_team`, extensible to any depth |
| Same repo/PR data duplicated on two different blueprints | Two integrations model the same real-world thing separately | Map by property into the existing blueprint instead of creating a second one |
| Agent can't tell which of several relations to a blueprint is which | Relations exist but have generic titles like `team` or `service` | Give each relation a semantic title (`Owned by`, `Runs in`, `Depends on`) |

## Quick reference

- Core layer first (organization, team, service), provider layer relates to
  it, never duplicates it.
- Hierarchy: one self-relation (`parent_team`), not one blueprint per tier.
- Ownership over relations: use `$team`, don't hand-roll a team relation.
- Mirror = one related value, surfaced as-is. Aggregation = computed across
  many, placed on the higher-abstraction blueprint.
- An ontology has descriptions, semantic relation titles, and typed
  properties; a schema is just field names.
- Integration = persistent, related, governed data. MCP connector = live,
  on-demand, unmodeled data. Use both, for different data.

Referenced files: 10 — file references are metadata, not package contents.

port-create-custom-oauth-app5.65 KB

View saved version →

---
name: port-create-custom-oauth-app
description: "Register an org-scoped external interface app (a public/PKCE Auth0 OAuth client) for a custom UI, such as a Lovable-, v0- or bolt.new-built app, using Port's MCP tools, then wire the Authorization Code + PKCE grant into that UI so it can sign users in and call the Port API. Use when asked to 'build a custom UI on top of Port', 'connect my Lovable app to Port', 'register an OAuth app in Port', 'add Port login to my app', 'get a client ID for my external app', or 'fix a Callback URL mismatch / Service not found error when logging in with Port'. Out of scope: Port's own plugin and custom widget system (this skill never builds or installs anything inside Port itself), and managing Port's SSO or identity-provider connections."
license: MIT
compatibility: "Claude Code, Cursor, Codex CLI, GitHub Copilot"
allowed-tools:
  - list_external_interface_apps
  - upsert_external_interface_app
  - revoke_external_interface_app
metadata:
  version: "1.0.0"
  author: port-labs
  repository: https://github.com/port-labs/port-skills
  tags: port,oauth,pkce,auth0,external-interface,custom-ui,mcp-powered
  summary: Register an OAuth app for a custom UI and wire up PKCE login
---

# Port custom OAuth app

Register an org-scoped **public/PKCE** OAuth client, an *external interface
app*, so a custom UI you build outside Port can authenticate its own users
and call the Port API directly. There is never a `client_secret`: these are
PKCE public clients by design, so never ask for or invent one.

Registering, updating, and revoking apps takes **org admin** rights.

## Prerequisites

- Go over the `port-getting-started` skill first if this is your first time
  working with Port.
- Port's MCP server connected, as an org admin. The three tools above are
  the only way this skill documents for registering an app, so there is no
  non-MCP fallback here. Without them, search `search_port_knowledge_sources`
  or [docs.port.io](https://docs.port.io) for the current alternative rather
  than guessing an endpoint.
- Register and test against the **published**, publicly reachable URL of the
  UI, not a login-gated live preview, or the login redirect never lands.

## How to set it up

1. **Collect the inputs, never guess them:** app name, 1 to 5 exact redirect
   URIs, and the Port region (EU, US, or other) that decides the Auth0 domain
   and API base URL. See
   [references/registering-the-app.md](references/registering-the-app.md).
2. **Check existing apps, then register:** call
   `list_external_interface_apps`, then `upsert_external_interface_app`
   without `appId`. Keep `id` and `clientId` straight, they are not
   interchangeable. Same reference.
3. **Wire the PKCE flow into the UI:** popup-based login, `code_verifier`
   kept in the opener's memory (never in `state` or a URL), token exchange
   from a server route, `audience` set to the region's API base. See
   [references/pkce-flow.md](references/pkce-flow.md). A complete React
   example is in [assets/spa-example.md](assets/spa-example.md), and
   [references/popup-login-explained.md](references/popup-login-explained.md)
   explains why the popup flow is built the way it is.
4. **Call the Port API** with the access token, and account for the CORS
   allowlist's propagation delay. See
   [references/pkce-flow.md#calling-the-port-api](references/pkce-flow.md#calling-the-port-api).
5. **Check the code against the verification checklist** before telling the
   user it is done. See
   [references/pkce-flow.md#verification-checklist](references/pkce-flow.md#verification-checklist).
6. **Add the published URL as a redirect URI** once the UI is published, and
   manage or revoke apps later. See
   [references/registering-the-app.md#managing-apps](references/registering-the-app.md#managing-apps).

## Common pitfalls

More symptoms and the common mistakes to push back on are in
[references/troubleshooting.md](references/troubleshooting.md).

| Symptom | Cause | Fix |
|---|---|---|
| `Callback URL mismatch` loop on login | `redirect_uri` is not an exact match for a registered URI, or it is a login-gated preview origin | Read `window.location.origin` from the running app and register that exact origin and path, and test from the published URL |
| `Service not found: <audience>` | Wrong `audience` for the environment | Use the region's API base as the audience, never production's by default |
| `/oauth/token` fails with a bare `Failed to fetch` | Auth0 CORS-blocks the browser call, the origin is not in the app's Allowed Web Origins | Do the token exchange from a server route |
| `404: User with id "me" was not found` | Port has no `/v1/users/me` | Read claims from the decoded `id_token`, or call `/v1/users/{email}` |

## Quick reference

- Tools: `list_external_interface_apps`, `upsert_external_interface_app`,
  `revoke_external_interface_app`. All need org admin.
- `redirectUris`: 1 to 5, exact, absolute, no wildcards, `https://` only
  (`http://` allowed for `localhost` / `127.0.0.1`). An update **replaces**
  the whole set.
- `id` goes to `appId`. `clientId` goes in the frontend as `client_id`.
- Full detail: [registering-the-app](references/registering-the-app.md),
  [pkce-flow](references/pkce-flow.md),
  [popup-login-explained](references/popup-login-explained.md),
  [troubleshooting](references/troubleshooting.md).

## Examples

- [assets/spa-example.md](assets/spa-example.md): complete React SPA login
  flow, with the minimal `LoginPage`.
- [assets/login-page-anchor-ui.md](assets/login-page-anchor-ui.md): the
  default styled `LoginPage` using `@port-labs/anchor-ui`.
- [assets/port-proxy-fallback.md](assets/port-proxy-fallback.md): same-origin
  proxy for when the calling origin isn't a registered redirect URI.

Referenced files: 7 — file references are metadata, not package contents.

port-dashboards11.4 KB

View saved version →

---
name: port-dashboards
description: >-
  Build and update Port dashboard pages: widgets (table, number chart, pie
  chart, bar chart, multi-line chart, markdown, iframe, links, action card,
  action-runs table, entity details, AI agent), the 12-column row layout,
  sidebar placement, and private-page rules. Covers dataset filter syntax so
  a widget shows the entities you expect instead of an empty or wrong
  result. Use when asked to "build a Port dashboard", "add a widget to a
  dashboard page", "create a number chart / pie chart / table widget in
  Port", "why is my Port widget empty", "make this dashboard page private",
  or "where should this dashboard go in the sidebar". For building a custom
  plugin widget (your own React iframe), use the nested
  `port-dashboard-plugins` skill instead.
license: MIT
compatibility: "Claude Code, Cursor, Codex CLI, GitHub Copilot"
metadata:
  version: "1.0.0"
  author: port-labs
  repository: https://github.com/port-labs/port-skills
  tags: port,dashboards,widgets,reference
  summary: Build Port dashboard pages with widgets, layout, and permissions
---

# Port dashboard pages

A Port dashboard page is a sidebar entry made of one or more widgets (tables,
charts, markdown, links, and more) arranged in a 12-column grid. This skill
is reference-only: static knowledge about widget schemas, the layout grid,
sidebar placement, and dataset filter syntax, drawn from Port's dashboard
page schema and public docs. It doesn't need a live Port account or MCP
server to produce a correct dashboard payload, only a real account to
actually create the page.

Out of scope: building your own custom-code widget (a React/TypeScript
iframe plugin uploaded via `port-plugins-cli`) is the nested
[`port-dashboard-plugins`](port-dashboard-plugins/SKILL.md) skill, not this
one. Blueprint and property modeling (what a `dataset` can filter on) is
owned by the `port-blueprints` skill; this skill only covers how those
properties map onto widget and dataset fields. The full search rule
grammar (all operators, `and`/`or`/`not` nesting) is Port's [Search and
query](https://docs.port.io/search-and-query/structure-and-syntax) syntax;
this skill covers the subset relevant to widget datasets. Who can **view or
edit** a page once it exists, including private pages shared with specific
users or teams, is the `port-permissions` skill's page-permissions
reference, not this one; this skill only covers a private page's own
creation and visibility mechanics (see [Step 5](#step-5---handle-private-pages-correctly)).

## Skill class

Reference skill: the widget schemas, grid rules, and dataset syntax below
are static and enough to hand-author a correct dashboard JSON payload. If
the [Port MCP server](https://docs.port.io/ai-interfaces/port-mcp-server/overview)
is connected, its dashboard tools (`upsert_dashboard_page`, `upsert_widget`,
`get_page`, `get_sidebar`, `load_widget_schema`, `list_entities`,
`list_blueprints`) are an optional enhancement: they create the page for
you and let you sample real entities to validate a dataset before saving.
Without them, use the fallback in every step below: the Port UI's dashboard
editor, or the [Pages API](https://docs.port.io/api-reference/pages) /
[Swagger](https://api.port.io/swagger/#Pages) directly.

## Prerequisites

- Go over the `port-getting-started` skill first if this is your first time
  working with Port.
- The blueprint(s) the dashboard's widgets will query already exist, with
  the properties you plan to filter, group, or chart on.
- Optional: the Port MCP server connected in your agent, for live dataset
  validation and one-shot page creation (see Skill class above). Without
  it, use the Port UI or the Pages API with a `client_id`/`client_secret`
  pair (**...** menu > **Credentials** in the Port app). Search
  `search_port_knowledge_sources` for anything this skill doesn't cover.

## Step 1 - Decide sidebar placement

Precondition: a new dashboard page needs a home in the sidebar.
Action: if MCP is available, call `get_sidebar` to inspect the existing
folder structure before creating the page. If a single folder clearly
matches the dashboard's topic, place the page there (`parent` on the page).
If several folders are plausible, ask the user which one they prefer
instead of guessing. If nothing matches, place the page at the sidebar
root, creating a new folder only when the dashboard starts a new category
of its own.
Fallback: without MCP, open the Port UI's sidebar and read the folder tree
directly, or `GET /v1/pages` (see [references/dashboards.md](references/dashboards.md#sidebar-placement-and-page-fields))
to list existing pages and their `parent`/`after` fields.

## Step 2 - Choose widgets and validate each dataset

Precondition: you know which entities and metrics the dashboard should
show.
Action: pick one widget type per metric or view from the twelve types in
[references/dashboards.md](references/dashboards.md#widget-types), matching the widget to the question
("how many" -> number chart, "trend over time" -> multi-line chart, "browse
rows" -> table). For every widget with a `dataset`, resolve the target
blueprint first, then validate the dataset **before** creating the widget:
if MCP is available, run `list_entities` with the dataset as `query`,
`countOnly: true`, and a small `limit` (3-5), to confirm the count and that
the returned entities look right. If the call errors or the results look
wrong, call `list_blueprints` to check property names, types, and enum
values, fix the dataset, and retry.
Fallback: without MCP, run the equivalent call yourself:
`POST /v1/blueprints/{blueprint}/entities/search` with the same `query`
body (see [references/dashboards.md](references/dashboards.md#dataset-filter-syntax) for the rule
grammar and examples), or build the widget in the Port UI and use its
built-in entity count/preview before saving.

## Step 3 - Lay out the page (12-column grid)

Precondition: the widget set for the page is known.
Action: arrange widgets into rows. Every row's column sizes must sum to
exactly 12, a row holds at most 4 columns, each column is 3-12 wide, and
every row is at least 400px tall. Every layout column's `id` must match a
widget's `id` exactly, one-to-one. See
[references/dashboards.md](references/dashboards.md#the-12-column-layout-grid) for the layout
schema and worked examples.
Fallback: the Port UI's drag-and-drop dashboard editor enforces these
constraints for you, use it when hand-writing a layout for 6+ widgets gets
error-prone.

## Step 4 - Create or update the page

Precondition: widgets and layout are ready.
Action: if MCP is available, call `upsert_dashboard_page` with the full
`identifier`, `title`, `widgets`, and `layout`. For 5+ widgets, try the full
payload first; if it fails on size or complexity, fall back to an
incremental flow: create an empty page (`widgets: []`), add widgets one at
a time with parallel `upsert_widget` calls, then call `get_page` and pass
its `widgets` straight through to a final `upsert_dashboard_page` call that
only adds the new `layout`.
Fallback: without MCP, create the page from the Port UI's dashboard editor,
or call the [Pages API](https://docs.port.io/api-reference/pages) directly:
`POST /v1/pages` (or `PATCH /v1/pages/{identifier}` if it already exists)
with `type: "dashboard"` and a single root `dashboard-widget` wrapping your
`widgets` and `layout` (see
[references/dashboards.md](references/dashboards.md#creating-a-page-with-the-raw-api) for the exact
envelope Port expects).

## Step 5 - Handle private pages correctly

Precondition: the dashboard is personal, not for the whole organization.
Action: set `visibility: "private"` and omit `parent` and `after`
entirely, whether via MCP or the raw API. Port keeps private pages in a
separate sidebar section and appends an owner-specific suffix to the
identifier in its response, use *that* returned identifier for any
follow-up edit, widget update, or delete, not the one you sent.
Fallback: from the UI, set **Visibility** to **Private** when creating the
page (the field is read-only after creation for non-admins); an org admin
must first grant the `create:private-pages` scope if the option isn't
visible. See [references/dashboards.md](references/dashboards.md#private-pages) for the full
rules and an example payload.

## Examples

Worked, complete JSON payloads for every widget type, a full layout, and
the raw API envelope are in
[references/dashboards.md](references/dashboards.md#widget-types).

## Common pitfalls

| Symptom | Cause | Fix |
|---|---|---|
| Widget renders empty or with the wrong entities | Dataset filters on the wrong property, wrong blueprint, or a typo'd enum value | Validate with `list_entities`/`entities/search` before creating the widget, see [Step 2](#step-2---choose-widgets-and-validate-each-dataset) |
| Page creation rejected: "column sizes must sum to 12" | A row's column `size` values don't add to 12 | Recompute the row, max 4 columns, each 3-12 wide, see [Step 3](#step-3---lay-out-the-page-12-column-grid) |
| Page creation rejected: row height too small | A row's `height` is under 400px | Set every row to at least 400 |
| Widget never renders / layout looks broken | A layout column's `id` doesn't match any widget's `id` | Keep widget `id`s and layout column `id`s in exact sync |
| Private page edits silently target the wrong page, or 404 | Used the `identifier` you sent instead of the one Port returned (owner-suffixed) | Store and reuse the identifier from the create/`get_page` response, see [Step 5](#step-5---handle-private-pages-correctly) |
| Private page creation rejected | `parent` or `after` was set alongside `visibility: "private"` | Omit both fields entirely for private pages |
| Number chart shows 0 or an error | `chartType`-specific required fields missing (e.g. `func`/`averageOf` for `aggregateByProperty`) | Check the per-`chartType` required fields in [references/dashboards.md](references/dashboards.md#number-chart-entities-number-chart) |
| Pie/bar chart property rejected | `property` isn't prefixed (`property#`, `relation#`, `scorecard#`, ...) or a meta field (`$identifier`, `$title`, `$team`) | Use the prefixed form, see [references/dashboards.md](references/dashboards.md#property-reference-syntax) |

## Quick reference

- Widget types: `table-entities-explorer`, `entity-details`,
  `action-card-widget`, `action-runs-table-widget`, `links-widget`,
  `ai-agent`, `markdown`, `iframe-widget`, `entities-number-chart`,
  `entities-pie-chart`, `bar-chart`, `multi-line-chart`. Full schema for
  each: [references/dashboards.md](references/dashboards.md#widget-types).
- Layout: rows of 1-4 columns, column `size` 3-12 summing to exactly 12 per
  row, row `height` >= 400px, column `id` matches a widget `id`.
- Visibility: `"org"` (default, sidebar-placeable via `parent`/`after`) or
  `"private"` (no `parent`/`after`, identifier gets an owner suffix).
- Dataset shape: `{ combinator: "and" | "or", rules: [...] }`, each rule a
  `property`/`relation`/`scorecard` clause. Full grammar:
  [references/dashboards.md](references/dashboards.md#dataset-filter-syntax).
- MCP tools (optional, live account): `get_sidebar`, `list_blueprints`,
  `list_entities`, `load_widget_schema`, `upsert_widget`,
  `upsert_dashboard_page`, `get_page`.
- Fallback without MCP: Port's UI dashboard editor, or the
  [Pages API](https://docs.port.io/api-reference/pages) (`POST`/`PATCH`/`GET`
  `/v1/pages`) and `POST /v1/blueprints/{blueprint}/entities/search` for
  dataset checks.
- For custom-code widgets instead of built-in widget types, see the nested
  [port-dashboard-plugins](port-dashboard-plugins/SKILL.md) skill.

Referenced files: 17 — file references are metadata, not package contents.

port-getting-started4.5 KB

View saved version →

---
name: port-getting-started
description: "Set up Port and connect it to your coding agent: sign up for a Port account, install Port's MCP server, and optionally the Port CLI. Use when asked to 'get started with Port', 'set up Port', 'connect Port MCP', 'install the Port MCP server', 'how do I use Port from Claude Code/Cursor/VS Code', or before using any other skill in this repo for the first time."
license: MIT
compatibility: "Claude Code, Cursor, Codex CLI, GitHub Copilot, VS Code"
metadata:
  version: "1.0.0"
  author: port-labs
  repository: https://github.com/port-labs/port-skills
  tags: port,setup,mcp,onboarding,reference
  summary: Sign up for Port and connect its MCP server to your coding agent
---

# Getting started with Port

Every other skill in this repo produces correct Port config either way, but
without Port's MCP server connected, you're copying that config in by hand.
With it connected, the agent can read your live account and apply changes
directly. Do this once, first.

## Prerequisites

None. This is the first skill to run.

## How to get started

1. **Sign up for a Port account** at [app.port.io](https://app.port.io) if
   you don't have one yet.
2. **Find your Port region.** Check your Port app's base URL: if it
   contains `us`, you're on the US region; otherwise you're on EU.

   | Region | MCP server URL |
   |---|---|
   | EU | `https://mcp.port.io/v1` |
   | US | `https://mcp.us.port.io/v1` |

3. **Connect Port's MCP server** to your coding agent, using the URL for
   your region from the table above:

   | Client | How to connect |
   |---|---|
   | Cursor | [Install EU](cursor://anysphere.cursor-deeplink/mcp/install?name=port-eu&config=eyJ1cmwiOiJodHRwczovL21jcC5wb3J0LmlvL3YxIn0=) or add an MCP server manually with your region's URL |
   | Claude (claude.ai / Claude Desktop) | [Install from the Claude directory](https://claude.ai/directory/8f5edd1c-c876-465e-a5b9-cc8e6d27dcb7), then enter your region's MCP server URL when prompted |
   | VS Code | Add `port-eu` or `port-us` as an MCP server using the URL for your region |
   | Claude Code (CLI) | `claude mcp add --transport http port-eu https://mcp.port.io/v1` (or `port-us` / `https://mcp.us.port.io/v1`) |
   | Any other MCP-compatible agent | Point it at your region's URL, or follow the [manual install docs](https://docs.port.io/agent-management/port-mcp-server/installation) |

   This repo's own `.mcp.json` already ships both `port-eu` and `port-us`
   servers, so installing this plugin gets you both — disable whichever
   region doesn't apply to your account.

4. **Optional: install the [Port CLI](https://github.com/port-experimental/port-cli).**
   Beyond MCP, it covers day-to-day resource management from the terminal,
   and `port skills sync` pulls any skills your org has already configured
   in Port straight into your coding agent's skill directory.
5. **Once connected, prefer Port's own tools over guesswork.** Every skill
   in this repo notes where it can call Port's MCP tools directly (to read
   live state or apply a change) instead of just handing you config to
   copy in. If a skill doesn't cover something you need, search Port's
   documentation from inside the agent with the `search_port_knowledge_sources`
   MCP tool rather than guessing.

## Common pitfalls

| Symptom | Cause | Fix |
|---|---|---|
| MCP server added but no Port tools show up | Most agents need a session restart after adding an MCP server | Restart the coding agent |
| The Cursor/VS Code install link does nothing when clicked | Some browsers block custom URI schemes by default | Use the [manual install docs](https://docs.port.io/agent-management/port-mcp-server/installation) instead |
| `claude mcp add` command not found | That command is Claude Code's own CLI, not Claude Desktop or claude.ai | Use the Claude directory link for Desktop/claude.ai instead |
| Tools work for one teammate but not another | Each person connected to the wrong region's server for their account | Re-check the Port app base URL and connect to that region's server instead |

## Quick reference

- Sign up: [app.port.io](https://app.port.io)
- MCP server URL (EU): `https://mcp.port.io/v1`
- MCP server URL (US): `https://mcp.us.port.io/v1`
- Manual install docs: [docs.port.io/agent-management/port-mcp-server/installation](https://docs.port.io/agent-management/port-mcp-server/installation)
- Port CLI: [github.com/port-experimental/port-cli](https://github.com/port-experimental/port-cli)
- Search Port's docs from inside a connected agent: the `search_port_knowledge_sources` MCP tool
port-integrations10.5 KB

View saved version →

---
name: port-integrations
description: "Configure and troubleshoot Port integrations: mapping YAML, resources, selectors, JQ-based entity mappings, relations, and advanced options like createMissingRelatedEntities and itemsToParse. Use when asked to 'write a Port mapping', 'map API or tool data to a Port blueprint', 'configure an Ocean integration mapping', 'add a relation to a Port mapping', 'parse an array into separate Port entities', 'fix a Port mapping JQ error', or 'why isn't my Port integration syncing the right entities or properties'. For installing a new integration end-to-end and populating the catalog for the first time, use the nested `port-install-integration` skill instead. For testing integration connectivity or validating credentials before a sync, use the nested `port-integration-test-connection` skill instead."
license: MIT
compatibility: "Claude Code, Cursor, Codex CLI, GitHub Copilot"
metadata:
  version: "1.0.0"
  author: port-labs
  repository: https://github.com/port-labs/port-skills
  tags: port,integrations,mapping,ocean,jq,mcp-powered
  summary: Configure and troubleshoot Port integration mapping
---

# Port integrations

This skill teaches you how to author, edit, and debug the mapping configuration for a
Port integration: the YAML that tells an [Ocean-based integration](https://docs.port.io/build-your-software-catalog/custom-integration/ocean-custom-integration/overview)
(or any Port integration) which API objects to fetch and how to turn them into Port
entities, using [JQ](https://jqlang.org/manual/) expressions.

## Use this skill when

- Writing a new mapping for an integration (`resources`, `selector`, `port.entity.mappings`).
- Adding or fixing a relation between two blueprints inside a mapping.
- Splitting an array field into multiple entities with `itemsToParse`.
- A mapping test fails, or entities sync with missing/wrong data.

Out of scope: creating the blueprints and relations themselves (a mapping can only map
into fields that already exist on a blueprint), and general JQ language tutorials beyond
what mapping needs (see [references/mapping.md](references/mapping.md) for the
patterns that come up most). Installing a new integration end-to-end — kind selection,
use-case discovery, and populating the catalog for the first time — is the nested
[`port-install-integration`](port-install-integration/SKILL.md) skill, not this one.
Testing integration connectivity or validating credentials (`test connection`) — is the
nested [`port-integration-test-connection`](port-integration-test-connection/SKILL.md)
skill, not this one.

## Prerequisites

- Go over the `port-getting-started` skill first if this is your first time
  working with Port.
- A Port account, with the target blueprint(s) already created.
- An installed integration (hosted or self-hosted Ocean integration, or any other Port
  integration that exposes a mapping) whose `kind`s you're mapping from. If none is
  installed yet, see the [Ocean custom integration overview](https://docs.port.io/build-your-software-catalog/custom-integration/ocean-custom-integration/overview)
  first: this skill assumes the integration exists and can return data.
- Port's [MCP server](https://docs.port.io/ai-interfaces/port-mcp-server/overview) connected
  is **optional**. When connected, use it to fetch live blueprints and raw data, apply
  the mapping directly, and test it before saving. When it isn't connected, every step
  below has a documented fallback using the Port UI or public API. Never treat a missing
  MCP connection as a hard stop. Search `search_port_knowledge_sources` for anything
  this skill doesn't cover.

## Step 1 - Identify the target blueprint and source kind

Precondition: you know which Port blueprint the data should land in, and which API object (`kind`) the integration exposes for it.
Action: if MCP is connected, call `list_blueprints` to confirm the blueprint exists and read its exact property and relation identifiers (mapping into a property that doesn't exist fails validation). Check the integration's documentation for its supported `kind`s.
Fallback: open the blueprint in Port's [data model page](https://app.port.io/settings/data-model) to read its schema, and check the integration's page under [sync data to catalog](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/) for its supported kinds.

## Step 2 - Get a raw data sample for the kind

Precondition: you need a real (or representative) example of what the API returns for this `kind`, to write correct JQ paths.
Action: if MCP is connected, call `get_integration_kinds_with_examples` for the integration to get real raw data. Write JQ expressions against the actual field names and nesting shown there, not assumptions.
Fallback: use **Test examples** in the mapping's YAML editor (`+ Add kind`) in the Port UI, paste a real payload from the source API's own documentation, or capture one request to the API directly.

## Step 3 - Write the resource, selector, and query filter

Precondition: you know the data source and which items should be ingested versus skipped.
Action: add a `resources` entry with the `kind`, then a `selector` block. Use integration-specific selector keys to narrow the API request itself (cheaper, faster), and the `query` JQ expression to filter the response (`"true"` ingests everything). Order resources so a relation's target blueprint is ingested first. See [assets/simple-mapping.yaml](assets/simple-mapping.yaml).
Fallback: not applicable, this step has no external dependency.

## Step 4 - Write the entity mappings

Precondition: the resource and selector from step 3 are in place.
Action: under `port.entity.mappings`, set `identifier`, `title`, `blueprint`, and `properties` as JQ expressions evaluated against each item. Wrap hyphenated property or identifier names in bracket notation (`.properties["my-field"]`, not `.my-field`, which JQ parses as subtraction). See [references/mapping.md](references/mapping.md) for common transformations.
Fallback: not applicable, this is static authoring.

## Step 5 - Add relations, if any

Precondition: the target blueprint of the relation is ingested by an earlier resource (or already exists in the context lake).
Action: use a direct identifier reference (`relations: { serviceOwner: .owner.id }`) when the API returns the related entity's identifier directly, or a search query rule (`combinator`/`rules`) when you only have one of its properties. Relations are **replaced** on every sync, an empty array clears the relation. See [assets/mapping-with-relations.yaml](assets/mapping-with-relations.yaml).
Fallback: skip the relation in the mapping and assign it manually in the Port UI if you want to manage it outside the integration entirely.

## Step 6 - Set advanced options, if needed

Precondition: related entities referenced by this mapping may not exist yet, or you're exploding an array into multiple entities.
Action: add root-level `createMissingRelatedEntities: true` to auto-create missing relation targets, `deleteDependentEntities: true` when a required relation means the source entity must be deleted alongside its target, or `itemsToParse` (plus optional `itemsToParseName` and `itemsToParseTopLevelTransform`) to turn one array field into several entities.
Fallback: not applicable, these are static YAML keys.

## Step 7 - Validate the mapping

Precondition: you have a draft mapping and a raw data example from step 2.
Action: if MCP is connected, call `test_integration_mapping` with the mapping and the example from `get_integration_kinds_with_examples`, and iterate until it returns the expected entity with no errors.
Fallback: use **Test mapping** in the Port UI's YAML editor against the same example, or save the mapping, trigger a resync, and inspect the result: `get_integration_event_logs` if MCP is connected, otherwise the sync's logs in the [data sources page](https://app.port.io/settings/data-sources). For self-hosted Ocean integrations, you can also run the integration locally in dry-run mode before pointing it at production.

## Step 8 - Diagnose a failing or incomplete sync

Precondition: entities are missing, incomplete, or a test/resync reports errors.
Action: work through [references/troubleshooting.md](references/troubleshooting.md), which covers the five common root causes (mapping errors, missing data, relation issues, permission issues, filter issues), each with an MCP-powered diagnostic path and a UI/API fallback.
Fallback: not applicable, this step is itself the fallback path.

## Common pitfalls

| Symptom | Likely cause | Fix |
|---|---|---|
| `must NOT have additional properties` on test | Typo'd YAML key, wrong nesting level | Compare against [configure-mapping.md](https://docs.port.io/build-your-software-catalog/customize-integrations/configure-mapping) structure |
| `Blueprint with identifier "X" was not found` | Blueprint doesn't exist, or identifier typo | Confirm with `list_blueprints` or the data model page |
| A relation silently disappears after sync | JQ for the relation evaluated to an empty array | Relations are replaced, not merged, every sync. Return the full desired set |
| `.my-property` mapping returns nothing | Hyphen parsed as subtraction by JQ | Use `.properties["my-property"]` bracket notation |
| Entity never appears despite valid JQ | `selector.query` evaluates to `false` for that item | Test the query alone against the sample data first |
| Related entity never gets created | Related blueprint's resource runs after this one, or doesn't exist yet | Reorder resources, or set `createMissingRelatedEntities: true` |

## Quick reference

A mapping is: root-level advanced options (`createMissingRelatedEntities`,
`deleteDependentEntities`), then a `resources` list. Each resource has a `kind`, a
`selector` (`query` plus integration-specific keys), and `port.entity.mappings`
(`identifier`, `title`, `blueprint`, `properties`, `relations`), with `itemsToParse` on
`port` when one array field should become several entities.

Two complete, copy-pasteable starting points:

- [assets/simple-mapping.yaml](assets/simple-mapping.yaml) - one resource, no relations.
- [assets/mapping-with-relations.yaml](assets/mapping-with-relations.yaml) - two
  resources, a direct-identifier relation, a search-query relation, and
  `createMissingRelatedEntities`.

See [references/mapping.md](references/mapping.md) for mapping guidance and JQ recipes,
and [references/troubleshooting.md](references/troubleshooting.md) for the diagnostic
playbook. Full concept reference:
[configure-mapping.md](https://docs.port.io/build-your-software-catalog/customize-integrations/configure-mapping).

Referenced files: 19 — file references are metadata, not package contents.

port-permissions6.29 KB

View saved version →

---
name: port-permissions
description: "Configure Port's role-based access control: context lake (blueprint/entity) permissions (read, register, update, or delete entities, down to a single property or relation) and page/dashboard view/edit permissions. Use when asked to 'set up Port permissions', 'restrict who can edit an entity', 'only let the owning team modify this blueprint', 'add a dynamic read policy to a blueprint', 'control who can view a Port dashboard or catalog page', or 'why can this user see/edit something they shouldn't'. Out of scope: permissions on a Workflow's `SELF_SERVE_TRIGGER` node, including its dynamic policy (`port-workflows` owns that), permissions for Port's legacy Actions & Automations product (deprecated in favor of Workflows, not covered by any skill in this repo), and inviting users or creating teams themselves (Port's platform administration docs own that, this skill only covers what roles and teams can be granted once they exist)."
license: MIT
compatibility: "Claude Code, Cursor, Codex CLI, GitHub Copilot"
metadata:
  version: "1.0.0"
  author: port-labs
  repository: https://github.com/port-labs/port-skills
  tags: port,permissions,rbac,security,reference
  summary: Configure Port's RBAC across the context lake and pages
---

# Port permissions

Port's RBAC shows up in two independent places that reuse the same
`roles`/`users`/`teams` shape: context lake entities (a blueprint's `read`,
`register`, `update`, `unregister`) and pages/dashboards (`read`, `update`).
This skill covers both, plus the roles and ownership model that backs them.

Permissions on a workflow's own `SELF_SERVE_TRIGGER` node are a separate,
simpler mechanism owned by the `port-workflows` skill, see
[its permissions reference](../port-workflows/references/permissions.md)
instead of this one.

## Prerequisites

- Go over the `port-getting-started` skill first if this is your first time
  working with Port.
- The blueprint or page whose permissions you're changing already exists.
- If Port's MCP server is connected, use `list_blueprints` to read current
  blueprint definitions before editing, and `simulate_blueprint_permissions`
  to test a context lake permission change before applying it. There is no
  MCP tool that writes permissions directly, every step below applies
  changes through the raw API, UI, or Terraform. Search
  `search_port_knowledge_sources` for anything this skill doesn't cover.

## How to configure it

1. **Know the two building blocks every permission type reuses**: Port's
   three roles (Admin, blueprint Moderator, Member) and the `$team`
   ownership meta-property that backs every `ownedByTeam` flag. See
   [references/roles-and-ownership.md](references/roles-and-ownership.md).
2. **Set context lake (blueprint/entity) permissions**: who can `read`,
   `register`, `update`, or `unregister` entities of a blueprint, down to a
   single property or relation, a dynamic `read` policy, and the built-in
   permission simulator. See
   [references/context-lake-permissions.md](references/context-lake-permissions.md).
3. **Set page and dashboard permissions**: who can view or edit a catalog
   or dashboard page. See
   [references/page-permissions.md](references/page-permissions.md).
4. **Setting permissions on a workflow trigger instead?** That's a
   different, simpler shape scoped to one `SELF_SERVE_TRIGGER` node, not
   this skill, use the `port-workflows` skill's
   [permissions reference](../port-workflows/references/permissions.md).

Steps 2 and 3 are independent, do whichever the task needs.

## Examples

- [assets/context-lake-permissions-ownership-and-policy.json](assets/context-lake-permissions-ownership-and-policy.json):
  a blueprint's `entities` permissions combining role/user/team grants,
  `ownedByTeam` registration, and a dynamic `read` policy.
- [assets/page-permissions-patch.json](assets/page-permissions-patch.json):
  a `PATCH` body giving a team `read` access and restricting `update` to
  Admins.

## Common pitfalls

| Symptom | Cause | Fix |
|---|---|---|
| Set `ownedByTeam` and a dynamic `read` policy on the same scope, only one works | When entity owner permissions are configured, the dynamic read policy is ignored | Pick one: `ownedByTeam` for team-based access, `policy` for anything else |
| Granular `updateProperties` grant has no effect | A global `update` permission is also set, global overrides granular | Remove the global `update` grant, or fold the property-level grant into it |
| User can't register a new entity even though `register` allows their role | The entity also has a required property or relation they lack `updateProperties`/`updateRelations` access to | Grant edit access to every required field, not just the top-level `register` scope |
| `PATCH`ing page permissions removed a role's/user's/team's access unexpectedly | `PATCH` replaces each key you include entirely; omitting an existing member of that key removes them | Include the full desired list for every key you send, not just the ones you're adding |
| Terraform permission resource conflicts with UI edits | `port_page_permissions` fully owns the resource once applied | Manage a given page's permissions from one place, Terraform or the UI, not both |
| Trying to add a dynamic `queries`/`conditions` policy to a workflow trigger | That shape belongs to Port's legacy, deprecated Actions & Automations permissions, not Workflows | Use the simpler `policy` (`combinator`/`rules` over `user`/`userTeams`/`form` context) documented in `port-workflows`' [permissions reference](../port-workflows/references/permissions.md) instead |

## Quick reference

- Three roles: Admin (everything), blueprint Moderator (everything on one
  blueprint), Member (read + execute workflows by default).
- Context lake entity permission scopes: `read`, `register`, `unregister`,
  `update`, `updateProperties.<property>`, `updateRelations.<relation>`.
  Each takes `roles`/`users`/`teams`/`ownedByTeam`; `read` also takes a
  dynamic `policy`. API: `GET`/`PATCH /v1/blueprints/{id}/permissions`.
- Page permission scopes: `read`, `update`. Each takes
  `roles`/`users`/`teams`. API: `PATCH /v1/pages/{id}/permissions`.
- For a workflow's `SELF_SERVE_TRIGGER` node permissions (a different,
  simpler shape scoped to one trigger), see the `port-workflows` skill's
  [permissions reference](../port-workflows/references/permissions.md)
  instead of this one.

Referenced files: 5 — file references are metadata, not package contents.

port-terraform11.2 KB

View saved version →

---
name: port-terraform
description: >-
  Author and manage Port blueprints, entities, scorecards, and self-service
  actions with the official Terraform provider (port-labs/port-labs).
  Covers provider setup and auth, every resource type (port_blueprint,
  port_entity, port_scorecard, port_action, port_webhook, port_integration,
  port_aggregation_properties), the create/update/delete/import lifecycle,
  and known limitations that produce broken or silently wrong config. Use
  when asked to "add a Port resource to Terraform", "manage Port as code",
  "write a port_blueprint/port_entity/port_action/port_scorecard resource",
  "import a Port blueprint into Terraform state", or "why did my Port
  terraform apply not do what I expected".
license: MIT
compatibility: "Claude Code, Cursor, Codex CLI, GitHub Copilot"
metadata:
  version: "1.0.0"
  author: port-labs
  repository: https://github.com/port-labs/port-skills
  tags: port,terraform,iac,reference
  summary: Manage Port resources as code with the Terraform provider
---

# Port Terraform provider

Port's Terraform provider (`port-labs/port-labs`) lets you define blueprints,
entities, scorecards, self-service actions, webhooks, and integration
mappings as `.tf` resources, the same way you'd define any other piece of
infrastructure. This skill is reference-only: static knowledge about the
provider's resource schema, drawn from the current v2.x provider and Port's
Terraform docs. It doesn't need a live Port account or Port MCP server to
produce correct HCL, only a real account and API credentials to actually
`terraform apply` it.

Out of scope: the underlying Port concepts (property types, relation
semantics) are owned by the `port-blueprints` skill; self-service action
JSON schema and invocation method payload templating aren't covered by a
skill in this repo yet. This skill only covers how those concepts map onto
Terraform arguments. Pulumi is Port's
other IaC provider, briefly noted in [Step 1](#step-1---set-up-the-provider),
but not covered in depth here.

## Prerequisites

- Go over the `port-getting-started` skill first if this is your first time working with Port.
- The [Terraform CLI](https://learn.hashicorp.com/tutorials/terraform/install-cli) (or [OpenTofu](https://opentofu.org/docs/intro/install/), a drop-in alternative, swap `terraform` for `tofu`).
- A Port account and a `client_id`/`client_secret` pair, from **...** menu > **Credentials** in the Port app, or the [API docs](https://docs.port.io/build-your-software-catalog/custom-integration/api#find-your-port-credentials).
- No Port MCP server or CLI is required to write or apply the `.tf` files this skill produces. If one is connected, use it to check the current live state (`list_blueprints`) before writing HCL that should match it, and search `search_port_knowledge_sources` for anything this skill doesn't cover.

## Step 1 - Set up the provider

Precondition: no `port` provider block exists in the working directory yet.
Action: add a `terraform` block pinning `port-labs/port-labs`, and a
`provider "port"` block with credentials from environment variables, then
run `terraform init`:

```hcl
terraform {
  required_providers {
    port = {
      source  = "port-labs/port-labs"
      version = "~> 2.4.0"
    }
  }
}

provider "port" {
  client_id = "" # or set the environment variable PORT_CLIENT_ID
  secret    = "" # or set the environment variable PORT_CLIENT_SECRET
  base_url  = "https://api.port.io"
}
 ```

```bash
terraform init
 ```

Fallback: if the user's stack is Python, TypeScript, JavaScript, or Go
instead of HCL, Port also ships a Pulumi provider with the same `Entity`
resource concept. Point them at
[docs.port.io's Pulumi page](https://docs.port.io/build-your-software-catalog/custom-integration/iac/pulumi/)
instead of forcing Terraform.

## Step 2 - Model data with `port_blueprint`

Precondition: the context lake needs a new type, or an existing blueprint needs
to move under Terraform management.
Action: write a `port_blueprint` resource with `identifier`, `title`, typed
`properties`, and any `relations` to other blueprints. See
[references/resource-reference.md](references/resource-reference.md) for
the full property-type and relation argument tables, and
[assets/blueprint.tf](assets/blueprint.tf) for a complete example.
Fallback: if a relation's `target` blueprint doesn't exist yet, define it
first, `port_blueprint.relations` requires an existing target and Terraform
won't infer creation order across two relations pointing at each other
without an explicit `depends_on`.

## Step 3 - Populate the context lake with `port_entity`

Precondition: a blueprint exists (via Terraform or otherwise) and you need
to create, update, or manage a specific instance of it.
Action: write a `port_entity` resource with `blueprint`, `title`, an
explicit `identifier`, and `properties`/`relations` matching the blueprint's
schema. See [assets/entity.tf](assets/entity.tf) for single and many
relations together.
Fallback: if you're bringing an entity that already exists in Port under
Terraform management, don't start from a partial definition, see
[Limitations #3](references/limitations.md) first: unspecified properties
get overridden with empty values, not left alone.

## Step 4 - Add scorecards with `port_scorecard`

Precondition: entities of a blueprint need a maturity or readiness score.
Action: write a `port_scorecard` resource with `blueprint`, `rules`
(`identifier`, `title`, `level`, `query`), and optionally custom `levels`.
Each rule's `query.conditions` is a list of `jsonencode()`'d condition
objects, not native HCL. See
[assets/scorecard.tf](assets/scorecard.tf).
Fallback: if a rule's `level` doesn't match one of the scorecard's `levels`
(or the defaults `Basic`/`Bronze`/`Silver`/`Gold` when `levels` is omitted),
`terraform apply` rejects the rule, define the levels first.

## Step 5 - Add self-service actions and automations with `port_action`

Precondition: users need a form to trigger a backend (self-service action),
or an event in Port needs to trigger one automatically (automation).
Action: write a `port_action` resource with exactly one of
`self_service_trigger` / `automation_trigger`, and exactly one invocation
method block (`webhook_method`, `github_method`, `gitlab_method`,
`azure_method`, `kafka_method`, `upsert_entity_method`, or
`integration_method`). See
[references/resource-reference.md](references/resource-reference.md) for
the full trigger and invocation-method argument tables, and
[assets/action.tf](assets/action.tf) for a self-service action plus an
automation in one file.
Fallback: if a `user_properties` entity-picker `dataset` mixes a static rule
with a `jq_query` rule, don't use the native HCL `dataset` block, wrap the
whole thing in `jsonencode()`, see
[Limitations #1](references/limitations.md).

## Step 6 - Sync webhooks, integrations, and aggregations

Precondition: you need a custom inbound webhook (`port_webhook`), to bring
an already-installed Ocean integration's mapping under version control
(`port_integration`), or to add computed count/average properties across
related entities (`port_aggregation_properties`).
Action: see the per-resource argument tables in
[references/resource-reference.md](references/resource-reference.md).
`port_integration` manages an *existing* installation only, always
`terraform import` it first (Step 7).
Fallback: a `port_aggregation_properties` property returns `null` or `0`
with no error when more than one relation path connects the two blueprints
and `path_filter` isn't set, see
[Limitations #2](references/limitations.md).

## Step 7 - Import existing Port resources into Terraform state

Precondition: a blueprint, entity, scorecard, action, webhook, integration,
or aggregation property already exists in Port and needs to come under
Terraform management.
Action: add a resource block (even an empty one to start), then run
`terraform import <resource_type>.<name> "<id>"` with the ID format from the
[import table](references/resource-reference.md#lifecycle-create-update-delete-import),
then `terraform plan` to check for drift before the next `apply`.
Fallback: if the resource block doesn't match the imported state,
`terraform plan` shows the mismatch as drift to fix, and applying it will
overwrite (or empty out) the real Port resource, don't apply until the plan
shows no unexpected changes.

## Examples

Complete, standalone `.tf` files (each includes its own `terraform` and
`provider` block):

- [assets/blueprint.tf](assets/blueprint.tf): two related blueprints, all five property-group types.
- [assets/entity.tf](assets/entity.tf): entities wired up with a single relation and a many relation.
- [assets/action.tf](assets/action.tf): a self-service create action with a webhook backend, plus an automation.
- [assets/scorecard.tf](assets/scorecard.tf): custom levels and multiple JQ-based rules.

## Common pitfalls

| Symptom | Cause | Fix |
|---|---|---|
| `port_action` config rejected, or built from a stale example | Top-level `user_properties`/`operation` on `port_action` is the deprecated v1.x shape | Nest under `self_service_trigger` or `automation_trigger`, see [Limitations #6](references/limitations.md) |
| Entity-picker dataset doesn't filter correctly | Mixed static and `jq_query` rules in a native HCL `dataset` block | `jsonencode()` the whole `dataset` object, see [Limitations #1](references/limitations.md) |
| Aggregation property is `null`/`0` with no error | Ambiguous relation path between blueprints | Add `path_filter`, see [Limitations #2](references/limitations.md) |
| Updating an existing entity wipes properties you didn't touch | `port_entity` uses create/override, not merge | Reproduce the entity's full current state in the resource body |
| `terraform destroy` fails on a blueprint | Entities exist that weren't created by this Terraform state | Set `force_delete_entities = true` before destroying |
| Changing a property's `type` is blocked | `blueprint_property_type_change_protection` defaults to `true` | Add a new property with the right type, migrate data, remove the old one, don't disable the protection |
| Drift wipes a resource right after `terraform import` | Resource block doesn't match the real imported state | Reconcile the block with `terraform plan` before the first `apply` |

## Quick reference

- Provider: `provider "port" { client_id, secret, base_url }`, or
  `PORT_CLIENT_ID`/`PORT_CLIENT_SECRET` env vars.
- Resources: `port_blueprint`, `port_entity`, `port_scorecard`,
  `port_action`, `port_webhook`, `port_integration`,
  `port_aggregation_properties`.
- Property groups (blueprint and entity): `string_props`, `number_props`,
  `boolean_props`, `object_props`, `array_props`.
- Relation groups (entity only): `single_relations`, `many_relations`.
- Action triggers: `self_service_trigger` (`CREATE`/`DAY-2`/`DELETE`) or
  `automation_trigger` (event-based). Exactly one per action.
- Action invocation methods: `webhook_method`, `github_method`,
  `gitlab_method`, `azure_method`, `kafka_method`, `upsert_entity_method`,
  `integration_method`. Exactly one per action.
- Import ID formats: full table in
  [references/resource-reference.md](references/resource-reference.md#lifecycle-create-update-delete-import).
- Full argument reference:
  [references/resource-reference.md](references/resource-reference.md).
  Known gotchas: [references/limitations.md](references/limitations.md).

Referenced files: 6 — file references are metadata, not package contents.

port-workflows11 KB

View saved version →

---
name: port-workflows
description: "Build Port workflows: node-based automations made of triggers (self-service forms, context lake events), action nodes (webhook, upsert entity, GitHub/GitLab/Azure DevOps/Claude managed agents integration actions, Kafka, Cursor Agent, AI), condition and input nodes, JQ templating between nodes, and self-service trigger permissions, authored as workflow JSON against the Port API. Use when asked to create a Port workflow, add a trigger to a workflow, add an action node, wire up a self-service workflow, add branching with a condition node, add an approval step to a workflow, build an event-driven automation in Port, or write workflow JSON. Port workflows are Port's own nodes-and-edges automation graph, not a CI/CD pipeline file like GitHub Actions."
license: MIT
compatibility: "Claude Code, Cursor, Codex CLI, GitHub Copilot"
metadata:
  version: "1.0.0"
  author: port-labs
  repository: https://github.com/port-labs/port-skills
  tags: port,workflows,automation,reference
  summary: Build Port workflows with triggers, action nodes, and conditions
---

# 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](#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](https://docs.port.io/workflows/overview#comparison-with-actions--automations)
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-started` skill first if this is your first time
  working with Port.
- To create or update workflows without MCP: a Port `CLIENT_ID` and
  `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](#step-6---create-or-update-via-the-port-api) 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](https://docs.port.io/workflows/overview) 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](references/node-types.md#trigger-nodes).
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](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](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](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` |

```bash
curl -L -X POST 'https://api.port.io/v1/workflows' \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H 'Content-Type: application/json' \
  --data @workflow.json
 ```

Fallback: 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](#common-pitfalls) below.

## Complete examples

[assets/self-service-deploy-workflow.json](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](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](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](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](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](references/permissions.md).
- Auth: `POST /v1/auth/access_token` with `clientId`/`clientSecret` for 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`.

Referenced files: 6 — file references are metadata, not package contents.

Release notes

Version 1.0.8: Port Agent Skills plus an optional authenticated Port EU MCP connection using OAuth Dynamic Client Registration (DCR).

Declared in the saved package. Remote tools may change independently.

Package details

Publisher declarations from the archived package. These are separate from our research and the live service's terms.

Package license
MIT
Package author
Port
Keywords
See keywords
Getting started skill
./skills/port-getting-started/SKILL.md
Commerce declaration
Does not support commerceThis does not establish whether access is free or paid.
Review scenarios
5 positive · 3 negativeScenario counts, not test results.

Declared capabilities

  • Work with Port blueprints, entities, and context lakes
  • Configure and troubleshoot Port integrations
  • Build Port workflows and dashboards
  • Manage Port permissions and Terraform configuration

Package observed Nov 3, 2026.

Technical details
First seen
Oct 28, 2026 · 12:00 UTC
Last seen
Nov 3, 2026 · 18:00 UTC
Collection status
Collected

plugin_asdk_app_6ae0640e1ddc8191851d134bfeb36884

Download plugin data (JSON)