Custom Roles

List, create, update, and delete custom project roles with granular permission and field-level access control.


Custom roles let you define precise permission sets for members of a workspace, beyond the built-in access levels. A role controls which sections a member sees (records, files, chat, docs, …), what they can do (invite, complete, delete), and—optionally—which records, lists, and fields they can view or edit. Roles are ProjectUserRole objects in the API; workspaces are Workspace objects.

You assign a role when you invite someone by passing its roleId with accessLevel: MEMBER. The role must belong to the same workspace as the invite.

This page covers five operations:

  • workspaceUserRoles — list the roles in one or more workspaces.
  • createWorkspaceUserRole — create a role.
  • copyWorkspaceUserRole — duplicate an existing role.
  • updateWorkspaceUserRole — change a role.
  • deleteWorkspaceUserRole — delete a role.

Request

Send every request to https://api.blue.app/graphql with your authentication headers. Custom roles are scoped to a workspace, so pass the workspace ID (or slug) in the operation input. Company and project arguments accept either an ID or a slug.

POST https://api.blue.app/graphql
blue-token-id: YOUR_TOKEN_ID
blue-token-secret: YOUR_TOKEN_SECRET
blue-org-id: YOUR_ORG_ID

List the roles in a workspace. The filter argument is required; supply projectId for a single workspace.

query ListProjectRoles {
  workspaceUserRoles(filter: { projectId: "project_123" }) {
    id
    name
    description
    allowInviteOthers
    canDeleteRecords
    isRecordsEnabled
  }
}

To query several workspaces at once, pass projectIds instead. Either field may be null, but you must pass the filter object itself. Roles are only returned for workspaces the calling user belongs to.

query ListRolesAcrossWorkspaces {
  workspaceUserRoles(filter: { projectIds: ["project_123", "project_456"] }) {
    id
    name
    project {
      id
      name
    }
  }
}

Parameters

ProjectUserRoleFilter

The required filter argument for workspaceUserRoles.

ParameterTypeRequiredDescription
projectIdStringNoSingle workspace ID or slug. Takes precedence over projectIds when set.
projectIds[String]NoMultiple workspace IDs or slugs, for a cross-workspace listing.

CreateProjectUserRoleInput

The input for createWorkspaceUserRole. Only projectId and name are required; every other field is optional and falls back to the default shown.

ParameterTypeRequiredDefaultDescription
projectIdString!Yes—Workspace the role belongs to (ID or slug).
nameString!Yes—Display name of the role.
descriptionStringNonullOptional description.
Action permissions
allowInviteOthersBooleanNofalseMay invite new users to the workspace.
allowMarkRecordsAsDoneBooleanNofalseMay mark records complete.
canCreateRecordsBooleanNotrueMay create records.
canDeleteRecordsBooleanNotrueMay delete records.
canDeleteFilesBooleanNotrueMay delete files.
canCreateListsBooleanNotrueMay create lists.
canEditListsBooleanNotrueMay rename and reorder lists.
Section access
isRecordsEnabledBooleanNofalseAccess to the records views.
isActivityEnabledBooleanNofalseAccess to the activity feed.
isChatEnabledBooleanNofalseAccess to chat.
isDocsEnabledBooleanNofalseAccess to docs.
isFilesEnabledBooleanNofalseAccess to files.
isFormsEnabledBooleanNofalseAccess to forms.
isPeopleEnabledBooleanNofalseAccess to the people/members section.
Visibility filters
showOnlyAssignedTodosBooleanNofalseMembers see only records assigned to them.
showOnlyMentionedCommentsBooleanNofalseMembers see only comments that mention them.
Field-level scoping
recordFilterRecordFilterInputNononeRestrict visible records to those matching a filter.
customFields[CreateProjectUserRoleCustomFieldInput!]NononePer–custom-field view/edit access.
todoLists[CreateProjectUserRoleTodoListInput!]NononePer-list view/edit/delete access.
todoFields[CreateProjectUserRoleTodoFieldInput!]NononePer built-in record field (assignee, due date, …) view/edit access.

RecordFilterInput

Restrict visible records to those matching a filter, using the same group structure as record queries.

ParameterTypeRequiredDescription
groups[TodoFilterGroupInput!]!YesOne or more filter groups; a record is visible if it matches.
groupLinks[FilterLogicalOperator!]NoAND / OR operators joining the groups. Defaults to an empty list.

The filter fails closed. A condition whose values are not filled in is ignored while the filter still has real conditions — but if ignoring it would leave the filter restricting nothing, the role sees no records, not every record. The same holds for a condition the workspace’s records cannot answer: it hides records rather than widening access.

showOnlyAssignedTodos combines with a caller-supplied assignee filter: in a list’s records read, the role narrows the caller’s filter (records must match it and be assigned to the member). In the workspace-level record query, it overrides the caller’s flat assigneeIds with “assigned to the member”.

CreateProjectUserRoleCustomFieldInput

Per-field access for a custom field (CustomField).

ParameterTypeRequiredDefaultDescription
customFieldIdString!Yes—The custom field to scope.
viewableBooleanNotrueMembers can read the field’s value.
editableBooleanNofalseMembers can change the field’s value.

CreateProjectUserRoleTodoListInput

Per-list access for a list (RecordList).

ParameterTypeRequiredDescription
todoListIdString!YesThe list to scope.
viewableBoolean!YesMembers can see the list.
editableBoolean!YesMembers can edit records in the list.
deletableBoolean!YesMembers can delete records in the list.

CreateProjectUserRoleTodoFieldInput

Per-field access for a built-in record field.

ParameterTypeRequiredDefaultDescription
fieldTypeTodoFieldType!Yes—Which built-in field (see enum below).
viewableBoolean!Yes—Members can read the field.
editableBoolean!Yes—Members can edit the field.

TodoFieldType values: DUE_DATE, ASSIGNEE, TAG, DEPENDENCY, CUSTOM_FIELD, DESCRIPTION, CHECKLIST, REFERENCED_BY, CUSTOM_FIELD_GROUP, TIME_TRACKING, CREATED_DATE, UPDATED_DATE, COMPLETED_DATE, PROJECTS, LAST_UPDATED_BY.

CopyWorkspaceUserRoleInput

The input for copyWorkspaceUserRole.

ParameterTypeRequiredDescription
projectIdString!YesThe workspace the role belongs to.
roleIdString!YesThe role to duplicate.

UpdateProjectUserRoleInput

The input for updateWorkspaceUserRole. Same fields as CreateProjectUserRoleInput, with two differences:

  • roleId: String! (required) — the role to update.
  • projectId: String! (required) — the workspace the role belongs to, unchanged.
  • name is optional here (String, not String!); omit it to keep the current name.

Any flag you omit keeps its existing value (the update merges over the stored role rather than resetting to defaults).

DeleteProjectUserRoleInput

ParameterTypeRequiredDescription
roleIdString!YesThe role to delete.
projectIdString!YesThe workspace the role belongs to.

Response

workspaceUserRoles returns an array of ProjectUserRole objects.

{
  "data": {
    "workspaceUserRoles": [
      {
        "id": "clm4n8qwx000008l0g4oxdqn7",
        "name": "External Contractor",
        "description": "Limited access for outside collaborators",
        "allowInviteOthers": false,
        "canDeleteRecords": false,
        "isRecordsEnabled": true
      }
    ]
  }
}

createWorkspaceUserRole and updateWorkspaceUserRole both return the ProjectUserRole.

{
  "data": {
    "createWorkspaceUserRole": {
      "id": "clm4n8qwx000008l0g4oxdqn7",
      "name": "External Contractor"
    }
  }
}

deleteWorkspaceUserRole returns a nullable Boolean — true on success.

{ "data": { "deleteWorkspaceUserRole": true } }

Returns — ProjectUserRole

FieldTypeDescription
idID!Unique role identifier.
uidString!Stable public identifier.
nameString!Role name.
descriptionStringRole description.
createdAtDateTime!When the role was created.
updatedAtDateTimeWhen the role was last updated.
projectWorkspace!The workspace the role belongs to.
Action permissions
allowInviteOthersBooleanMay invite users.
allowMarkRecordsAsDoneBooleanMay complete records.
canCreateRecordsBooleanMay create records.
canDeleteRecordsBooleanMay delete records.
canDeleteFilesBooleanMay delete files.
canCreateListsBooleanMay create lists.
canEditListsBooleanMay edit lists.
Section access
isRecordsEnabledBooleanRecords access.
isActivityEnabledBooleanActivity access.
isChatEnabledBooleanChat access.
isDocsEnabledBooleanDocs access (every page, legacy wiki pages included).
isFilesEnabledBooleanFiles access.
isFormsEnabledBooleanForms access.
isPeopleEnabledBooleanPeople section access.
Visibility filters
showOnlyAssignedTodosBooleanRestricts records to those assigned to the member.
showOnlyMentionedCommentsBooleanRestricts comments to those mentioning the member.
Field-level scoping
recordFilterProjectUserRoleRecordFilterFilter-based record visibility (groups, groupLinks).
customFields[CustomField!]Custom fields scoped by the role.
todoLists[ProjectUserRoleTodoList!]Per-list access (viewable, editable, deletable).
todoFields[ProjectUserRoleTodoField!]!Per built-in field access (fieldType, viewable, editable).

Full example

Create a contractor role: enable the records and files sections, allow completing records but not deleting them, limit visibility to assigned records, and grant view-only access to one custom field.

mutation CreateContractorRole {
  createWorkspaceUserRole(
    input: {
      projectId: "project_123"
      name: "External Contractor"
      description: "Limited access for outside collaborators"
      isRecordsEnabled: true
      isFilesEnabled: true
      allowMarkRecordsAsDone: true
      canDeleteRecords: false
      canDeleteFiles: false
      showOnlyAssignedTodos: true
      customFields: [{ customFieldId: "field_123", viewable: true, editable: false }]
    }
  ) {
    id
    name
    isRecordsEnabled
    canDeleteRecords
  }
}

Restrict a role to records tagged tag_123 only:

mutation CreateScopedRole {
  createWorkspaceUserRole(
    input: {
      projectId: "project_123"
      name: "Client Reviewer"
      isRecordsEnabled: true
      recordFilter: { groups: [{ tagIds: ["tag_123"] }], groupLinks: [] }
    }
  ) {
    id
    name
  }
}

Rename an existing role and turn off chat without touching its other settings:

mutation UpdateRole {
  updateWorkspaceUserRole(
    input: {
      roleId: "role_123"
      projectId: "project_123"
      name: "Contractor (Phase 2)"
      isChatEnabled: false
    }
  ) {
    id
    name
    isChatEnabled
  }
}

Delete a role:

mutation DeleteRole {
  deleteWorkspaceUserRole(input: { roleId: "role_123", projectId: "project_123" })
}

Copy a role

Use copyWorkspaceUserRole to duplicate a role inside the same workspace. It returns the new WorkspaceUserRole!.

mutation CopyRole {
  copyWorkspaceUserRole(input: { projectId: "project_123", roleId: "role_123" }) {
    id
    name
    description
  }
}
{
  "data": {
    "copyWorkspaceUserRole": {
      "id": "clm4n8qwx000008l0g4oxdqn7",
      "name": "Copy of Contractor",
      "description": "Limited access for external contractors"
    }
  }
}

The copy carries over every permission flag and every scoped grant on the source role: custom-field access, list access, default-field access, saved-view access, tag rules, and the record visibility filter. It does not carry over the role’s members — the copy starts with nobody assigned.

The new role is named Copy of <source name>. Because name is a 191-character column, Blue truncates the source name so the prefix fits; it does not fail on a long name. You cannot choose the name in this call — use updateWorkspaceUserRole afterwards to rename it.

A copy counts toward the workspace’s custom-role limit, so this mutation can return PLAN_LIMIT_REACHED.

Assigning a role

A role is attached to a member at invite time. Set accessLevel: MEMBER and pass the role’s roleId to inviteUser. The role’s projectId must match the invite’s workspace, or the call fails with PROJECT_USER_ROLE_NOT_FOUND. Custom roles are treated as MEMBER-level for the access hierarchy.

mutation InviteWithRole {
  inviteUser(
    input: {
      email: "contractor@example.com"
      projectId: "project_123"
      accessLevel: MEMBER
      roleId: "role_123"
    }
  )
}

Errors

CodeWhen
FORBIDDENThe caller is not a workspace OWNER or ADMIN. Mutations require manage-roles access.
PROJECT_NOT_FOUNDThe projectId does not match a workspace.
PROJECT_USER_ROLE_NOT_FOUNDThe roleId does not exist, or belongs to a different workspace than projectId.
PLAN_LIMIT_REACHEDThe workspace has reached its custom-role limit. Message: Custom roles per workspace limit reached (current/limit). Upgrade your plan for more.

The custom-role limit is plan-dependent — roughly 1 on starter plans up to ~50 on enterprise, and unlimited on some plans. Upgrade the organization’s plan to raise it.

{
  "errors": [
    {
      "message": "Custom roles per workspace limit reached (1/1). Upgrade your plan for more.",
      "extensions": {
        "code": "PLAN_LIMIT_REACHED",
        "kind": "over_limit",
        "resource": "Custom roles per workspace",
        "current": 1,
        "limit": 1
      }
    }
  ]
}

Permissions

OperationRequired access
workspaceUserRolesAny member of the queried workspace (roles for workspaces you don’t belong to are silently excluded).
createWorkspaceUserRoleWorkspace OWNER or ADMIN.
copyWorkspaceUserRoleWorkspace OWNER or ADMIN.
updateWorkspaceUserRoleWorkspace OWNER or ADMIN.
deleteWorkspaceUserRoleWorkspace OWNER or ADMIN.