Dynamic permissions
Workflow permissions allow you to control who can see and execute a workflow from Port's self-service page. Workflows that a user does not have permission to execute are hidden entirely from their self-service page.
This page covers workflow-specific execute permissions (who can trigger a run). For controlling who can view or edit workflow definitions, see workflow catalog and ownership. For general Port RBAC, see manage users & teams.
Common use cases
- Allow only members of a specific team to execute a workflow.
- Restrict a workflow to Admin only.
- Dynamically grant access based on the user's properties (e.g., department, team).
- Allow access only when a specific form input value is provided.
- Restrict a workflow to specific execution sources, such as the Port UI only, API only, or MCP only.
How it works
Permissions are set in the trigger node's config when creating or updating a workflow via POST /workflows or PUT /workflows/:id.
Admins can always execute all workflows, regardless of the permissions configuration.
Default behavior
permissions value | Who can execute |
|---|---|
Not set or {} | Admin only |
{ "roles": ["Member"] } | Admin + Member |
{ "users": [...] } | Admin + listed users |
{ "teams": [...] } | Admin + listed teams |
{ "policy": {...} } | Admin + users matching the policy |
- Static fields (
roles,users,teams) are checked first using OR logic. Any match grants access. - Policy is only evaluated if static fields don't match.
- Organization machine tokens bypass static checks when no
policyis present. Personal access tokens are evaluated like interactive users and do not bypass static checks.
Configuration
Permissions are set inside the trigger node's config object:
{
"identifier": "my-workflow",
"title": "My Workflow",
"nodes": [
{
"identifier": "trigger",
"title": "Start",
"config": {
"type": "SELF_SERVE_TRIGGER",
"permissions": {
"roles": ["Member"],
"users": [],
"teams": []
},
"userInputs": {
"properties": {}
}
}
}
],
"connections": []
}
Static assignment
Grant access to specific roles, users, or teams. Use Port user IDs (not emails).
{
"permissions": {
"roles": ["Member"],
"users": ["user-id-1", "user-id-2"],
"teams": ["platform-team", "sre-team"]
}
}
Admin only
Two ways to restrict to Admin only:
Explicit empty object:
{
"permissions": {}
}
Omit the field:
{
"config": {
"type": "SELF_SERVE_TRIGGER",
"userInputs": { "properties": {} }
}
}
Dynamic permissions (policy)
Use policy for runtime evaluation based on user properties, team membership, or form inputs.
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "user", "property": "department" },
"operator": "=",
"value": "engineering"
}
]
}
}
}
The policy object provides an additional access path beyond static fields.
Policy structure
{
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "<context>", "property": "<property>" },
"operator": "<operator>",
"value": "<value>"
}
]
}
}
Contexts
| Context | Resolves to |
|---|---|
user | The executing user's properties (from the _user blueprint) |
userTeams | The executing user's team properties (from the _team blueprint) |
form | The workflow trigger's form input values |
workflowRun | Workflow run metadata, including the execution source (UI, API, or MCP) |
Use the source property with the workflowRun context to compare against a specific execution source value.
Dot notation for entity inputs
For entity-type form inputs, use dot notation to access entity properties:
| Syntax | Type |
|---|---|
inputName | string |
inputName.$identifier | string |
inputName.$title | string |
inputName.$team | array - use contains/notContains |
inputName.propertyName | actual blueprint type |
Deep traversal (input.relation.nested) is not supported, use mirror or calculation properties instead.
Execution source
Port evaluates workflow permissions against the execution source, which reflects how the run is being invoked:
| Source | When it applies |
|---|---|
UI | A user triggers the workflow from the Port UI, such as the self-service hub, an entity bolt menu, or a pre-filled workflow URL. |
API | A workflow run is created through the API, including organization machine tokens and personal access tokens. |
MCP | A workflow is invoked through the Port MCP server, for example by an AI agent. |
When Port lists self-service triggers or validates whether a user can execute a workflow, it resolves the execution source from the request context. Use the workflowRun context with the source property in a policy when you need different access rules for UI, API, and MCP invocations.
Operators
| Operator | Description |
|---|---|
= | Equals |
!= | Not equals |
>, <, >=, <= | Comparisons (numbers/strings) |
in | Scalar property is in array value |
notIn | Scalar property is not in array value |
contains | Array property contains scalar value (use for $team) |
notContains | Array property does not contain scalar value |
containsAny | Array property shares at least one element with another array value |
empty | Value is empty |
notEmpty | Value is not empty |
Examples
Allow only a specific team (static)
{
"permissions": {
"teams": ["platform-team"]
}
}
Allow based on team membership (policy)
Only allow users who are a member of the platform-team team (requires $identifier property on the _team blueprint):
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "userTeams", "property": "$identifier" },
"operator": "contains",
"value": "platform-team"
}
]
}
}
}
Restrict to members of entity's owning team
Only allow users who belong to the same team that owns the selected service entity:
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "form", "property": "service.$team" },
"operator": "containsAny",
"value": { "context": "userTeams", "property": "$identifier" }
}
]
}
}
}
Restrict based on entity team ownership
Only allow when the selected cluster entity belongs to platform-team:
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "form", "property": "cluster.$team" },
"operator": "contains",
"value": "platform-team"
}
]
}
}
}
Restrict based on form input
Only allow when user selects production environment:
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "form", "property": "environment" },
"operator": "=",
"value": "production"
}
]
}
}
}
Restrict to UI execution only
Only allow users to run the workflow from the Port UI. API and MCP invocations are blocked:
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "workflowRun", "property": "source" },
"operator": "=",
"value": "UI"
}
]
}
}
}
Block MCP execution
Allow UI and API invocations, but block runs triggered through the Port MCP server:
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "workflowRun", "property": "source" },
"operator": "!=",
"value": "MCP"
}
]
}
}
}
Restrict based on user property
Only allow users in the engineering department (requires department property on _user blueprint):
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "user", "property": "department" },
"operator": "=",
"value": "engineering"
}
]
}
}
}
Restrict to specific entity
Only allow when a specific cluster is selected (requires cluster entity input):
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "form", "property": "cluster.$identifier" },
"operator": "=",
"value": "prod-cluster"
}
]
}
}
}
Combined static + policy
The member role always has access. Non-members need to be in the SRE department:
{
"permissions": {
"roles": ["Member"],
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "user", "property": "department" },
"operator": "=",
"value": "sre"
}
]
}
}
}
Static fields are checked first. Policy only runs if static fields don't match.
Multiple conditions (OR)
Allow if the user is in the engineering or SRE department:
{
"permissions": {
"policy": {
"combinator": "or",
"rules": [
{
"property": { "context": "user", "property": "department" },
"operator": "=",
"value": "engineering"
},
{
"property": { "context": "user", "property": "department" },
"operator": "=",
"value": "sre"
}
]
}
}
}
Or use in operator for cleaner syntax:
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "user", "property": "department" },
"operator": "in",
"value": ["engineering", "platform", "sre"]
}
]
}
}
}
Restrict to members of entity's owning team (dynamic)
Allow execution only if the executing user belongs to one of the teams that own the selected entity. Requires an entity-type form input (e.g., service).
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "form", "property": "service.$team" },
"operator": "containsAny",
"value": { "context": "userTeams", "property": "$identifier" }
}
]
}
}
}
form.service.$team resolves to the array of teams that own the selected service entity. userTeams.$identifier resolves to the array of all team identifiers the executing user belongs to. The rule passes if those two arrays share at least one element.
Restrict to managers of team-owned entities
Allow execution only if the executing user is the manager of the team that owns the selected entity.
Add a mirror property to your blueprint that follows the relation path to the manager's identifier (e.g., service -> _team -> manager(_user) -> $identifier), then compare it against the executing user's identity:
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
// Mirror property follows: service -> _team -> manager (_user) -> $identifier
"property": {
"context": "form",
"property": "service.team_manager_id"
},
"operator": "=",
"value": { "context": "user", "property": "$identifier" }
}
]
}
}
}
form.service.team_manager_id resolves to the manager's email (via the mirror property). user.$identifier resolves to the executing user's email. The rule passes only if they match.
Restrict to managers or team leads
Allow execution only if the executing user has a specific role. Requires a role (or equivalent) property on the _user blueprint:
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
"property": { "context": "user", "property": "role" },
"operator": "in",
"value": ["manager", "team-lead", "director"]
}
]
}
}
}
The properties available in the user and userTeams contexts depend on what properties you have defined on your _user and _team blueprints. You can add custom properties such as role, department, or is_manager to those blueprints and use them in workflow permissions.
Restrict to senior roles
Allow execution only for senior engineers (for example, to gate production deployments):
{
"permissions": {
"policy": {
"combinator": "and",
"rules": [
{
// Requires a 'seniority' property on the _user blueprint
"property": { "context": "user", "property": "seniority" },
"operator": "in",
"value": ["senior", "staff", "principal"]
}
]
}
}
}
If you want to block execution based on catalog state (for example, preventing a workflow from running if an entity with the given name already exists), that is a runtime validation, not a permissions check. Use a condition node as the first step in your workflow to query the catalog and stop the run if the condition is not met.