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-dashboardsBefore 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 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` 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 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).
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 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 / Swagger directly.
Prerequisites
- Go over the
port-getting-startedskill 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) 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, 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 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 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 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 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 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.
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 |
| 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 |
| 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 ids and layout column ids 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 |
| 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 |
| 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 |
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.
- Layout: rows of 1-4 columns, column
size3-12 summing to exactly 12 per
row, row height >= 400px, column id matches a widget id.
- Visibility:
"org"(default, sidebar-placeable viaparent/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.
- 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 (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 skill.




