---
title: "POST /portfolio/{portfolioId}/views"
method: POST
path: "/portfolio/{portfolioId}/views"
tags: ["Views"]
---

# POST /portfolio/{portfolioId}/views

`POST /portfolio/{portfolioId}/views`

## Request

- Base URL: `https://api.craft.io`
- URL: `https://api.craft.io/portfolio/{portfolioId}/views`
- Auth: API key in header `x-api-key`

Create a portfolio view and return a link that opens it.

## Path parameters

- `portfolioId` string, required

## Request body

- ViewCreateBody
  - `name` string, required
  - `viewType` string: One of table, kanban, timeline, swimlanes, okr-table. Defaults to table.
  - `ownerType` 'personal' | 'workspace': `personal` (default) or `workspace`. Workspace views need admin rights.
  - `category` string: Category name from the list endpoint. Required for workspace views.
  - `filters` ViewFiltersInput: A view's filters, in the same vocabulary the item list endpoints use — so a filter you can pass to list_items works here unchanged, and vice versa. Every list takes ids OR names (names are resolved, and an unknown one is a 422 naming the valid values). An empty list clears that filter. `"-1"` means "items with no value set", the same sentinel the app uses. Portfolio views: `assignee` does not apply, and quarter / sprint / importance are matched by name because a portfolio merges them across its workspaces.
    - `type` string[]: Item types — the view's hierarchy control. e.g. `epic`, `feature`, `story`.
    - `status` string[]
    - `importance` string[]
    - `quarter` string[]: Release / quarter.
    - `sprint` string[]
    - `assignee` string[]: Workspace views only.
    - `labels` string[]
    - `createdBy` string[]
    - `epic` string[]: Ancestor epic ids.
    - `product` string[]: Product / theme names or ids — the Product an item sits under. Not the same as `workspace`.
    - `workspace` string[]: Source workspace ids — which workspace a row came from. Portfolio views only.
    - `initiative` string[]: Linked portfolio initiative ids.
    - `objectives` string[]
    - `keyResults` string[]
    - `persona` string[]
    - `statusCategory` string[]: Workflow bucket: `open`, `inProgress`, `done`.
    - `dependencies` string[]: Dependency Proximity: `none`, `related`, `blockedBy`, `blocks`.
    - `startDate` ViewDateRange
      - `from` string: ISO 8601 date.
      - `to` string: ISO 8601 date.
    - `endDate` ViewDateRange
      - `from` string: ISO 8601 date.
      - `to` string: ISO 8601 date.
    - `customFields` ViewCustomFieldFilter[]
      - `id` string, required: Field id from get_workspace_custom_fields / get_portfolio_fields.
      - `values` string[], required: Option ids or labels.
  - `controllers` ViewControllersInput
    - `sortBy` string: Sort axis, e.g. `status`, `importance`, `hierarchy`, `custom:<fieldId>`.
    - `sortDirection` 'asc' | 'desc'
    - `groupBy` string: Grouping axis. Not available on timeline views. `none` clears it.
    - `swimLanesBy` string: Swim-lane axis. Timeline and swimlanes views only. `none` clears it — the app's "None".
  - `columns` string[]: Visible columns, table and okr-table only.
  - `layout` string[]: Card fields, kanban / timeline / swimlanes only.
  - `columnColors` object: Per-column colours as `#RRGGBB`, keyed like `columns`. Send `{}` to clear. Duplicating a view: pass what get_view returned, or the copy loses them.
  - `compactMode` string: Card density, kanban / timeline / swimlanes only: `compact` or `default`.

## Response `200`

The created view

- ViewDetail
  - `viewId` string, required
  - `name` string, required
  - `viewType` string, required
  - `ownerType` 'personal' | 'workspace', required
  - `category` string
  - `workspaceId` string
  - `portfolioId` string
  - `webUrl` string
  - `controllers` ViewControllers, required
    - `sortBy` string
    - `sortDirection` 'asc' | 'desc'
    - `groupBy` string
    - `swimLanesBy` string
    - `sortByName` string: Display names for whichever of the axes above is a custom field. Every other axis already comes back as a name — "status", "importance" — and only a custom field is returned as `custom:<id>`, because that id is also what the write path takes. So a reader had an opaque number and no way to say what the view was grouped by without a second call, and an assistant describing a view printed the id at the user. Added alongside rather than replacing the value: `custom:<id>` is the input format axisToWire parses, and external clients read a view and write it back. Changing it would break them.
    - `groupByName` string
    - `swimLanesByName` string
  - `columns` string[]
  - `layout` string[]
  - `compactMode` string: Card density on a card-based view: `compact` or `default`.
  - `filters` RecordStringUnknown, required: Construct a type with a set of properties K of type T
  - `listItemsParams` RecordStringString, required: Construct a type with a set of properties K of type T
  - `listItemsTool` 'list_items' | 'list_portfolio_items' | 'list_feedback_items', required: Which list tool `listItemsParams` belongs to. Feedback views are the reason this is stated rather than assumed — their items are not work items.
  - `unsupportedFilters` UnsupportedFilter[]: Filters the list endpoints cannot reproduce — the result will be wider.
    - `filter` string, required
    - `reason` string, required
  - `inactiveControllers` InactiveController[]: Axes the view stores but the app does not apply, so they are left out of `controllers` — which then reads the way the app shows the view.
    - `controller` 'groupBy' | 'swimLanesBy', required
    - `storedValue` string, required
    - `reason` string, required
  - `columnColors` RecordStringString: Construct a type with a set of properties K of type T
  - `raw` WireView: A view exactly as the views service stores it. Unknown props round-trip.
    - `id` string
    - `name` string
    - `categoryId` string
    - `workspaceId` string
    - `personId` string, nullable
    - `ownerType` string
    - `viewType` string
    - `orderIndex` string
    - `category` string, nullable
    - `nameId` string, nullable
    - `description` string, nullable
    - `viewFields` string[], nullable
    - `compactViewFields` string[], nullable
    - `compactMode` string, nullable: Card density on a card-based view; empty string means never set.
    - `groupBy` string, nullable
    - `groupByCustomFieldId` string, nullable
    - `secondaryGroupBy` string, nullable
    - `secondaryGroupByCustomFieldId` string, nullable
    - `sortBy` string, nullable
    - `sortByCustomFieldId` string, nullable
    - `reverse` boolean, nullable
    - `isChanged` boolean
    - `okrHierarchy` string, nullable
    - `fieldFilters` RecordStringUnknown: Construct a type with a set of properties K of type T
    - `filters` RecordStringUnknown: Construct a type with a set of properties K of type T

## Other responses

- `422`: Invalid view

## Changes

- **2026-11-02** `2972a1b05ab1`: 1 info
  - added the optional property `inactiveControllers` to the response with the `200` status
- **2026-10-20** `10d02d3ad149`: 3 info
  - added the optional property `controllers/groupByName` to the response with the `200` status
  - added the optional property `controllers/sortByName` to the response with the `200` status
  - added the optional property `controllers/swimLanesByName` to the response with the `200` status
- **2026-09-10** `0ded52245043`: 4 info
  - added the new optional request property `columnColors`
  - added the new optional request property `filters/dependencies`
  - added the new optional request property `filters/product`
  - added the optional property `columnColors` to the response with the `200` status
- **2026-08-19** `df0d980eb1e3`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/craft/apis/craft-io-public-api/changes/portfolio/:portfolioId/views/post.md)

---

[API](https://skmtc.dev/craft/apis/craft-io-public-api.md) - [All operations](https://skmtc.dev/craft/apis/craft-io-public-api/llms.txt) - [OpenAPI document](https://skmtc.dev/craft/apis/craft-io-public-api/revisions/2972a1b05ab1?raw)
