Developer Portal for YouTrack and Hub Help

Automate Work Handoffs

You can use workflows to support a team process where each issue moves through a fixed sequence of states. This lets your team use issue states as a queue and as a record of who is responsible for the next step.

Suppose your team is setting up a process for handling user requests. This process is reflected in the columns of the agile board that is used by the development team.

Workflow agile statemachine

For example, suppose your team uses the following process:

  • New issues are assigned the Submitted state.

  • The team decides that the issue has to be addressed and changes the state to Open. Issues in this state are added to the work queue.

  • When developers have time to work on an issue, they assign it to themselves. The workflow then moves the issue to the In Progress state.

  • After the problem is fixed, the developer changes the state to Fixed. The workflow assigns the issue to a QA engineer.

  • When the tests are passed, the QA engineer sets the state to Verified.

This process can be implemented with two workflow rules. A state-machine rule enforces the order of state transitions, while an on-change rule moves open issues to In Progress when a developer assigns an issue to themselves.

Define the State Transitions

The first rule defines the allowed transitions for the State field. It also assigns the issue to the configured QA engineer when a developer moves the issue to Fixed.

Before you use this sample, replace qa.engineer with the login of the QA engineer who should verify fixed issues. This user must be available in the set of values for the Assignee field in every project that uses the workflow.

const entities = require('@jetbrains/youtrack-scripting-api/entities'); const workflow = require('@jetbrains/youtrack-scripting-api/workflow'); const getQAEngineer = (ctx) => { const assigneeField = ctx.issue.project.findFieldByName(ctx.Assignee.name); return assigneeField && assigneeField.findValueByLogin(ctx.QAEngineer.login); }; const assignToQAEngineer = (ctx) => { const qaEngineer = getQAEngineer(ctx); workflow.check(qaEngineer, 'Add the QA engineer to the set of values for the Assignee field.'); ctx.issue.fields.Assignee = qaEngineer; }; const currentUserIsQAEngineer = (ctx) => { const isQAEngineer = ctx.currentUser.login === ctx.QAEngineer.login; if (!isQAEngineer) { workflow.message('Only the QA engineer can complete this transition.'); } return isQAEngineer; }; exports.rule = entities.Issue.stateMachine({ title: 'Manage work handoffs by state', fieldName: 'State', states: { Submitted: { initial: true, transitions: { open: { targetState: 'Open' } } }, Open: { transitions: { start: { targetState: 'In Progress' } } }, 'In Progress': { onEnter: (ctx) => { if (!ctx.issue.fields.Assignee) { ctx.issue.fields.Assignee = ctx.currentUser; } }, transitions: { fix: { targetState: 'Fixed' }, reopen: { targetState: 'Open' } } }, Fixed: { onEnter: assignToQAEngineer, transitions: { verify: { targetState: 'Verified', guard: currentUserIsQAEngineer }, reopen: { targetState: 'Open', guard: currentUserIsQAEngineer } } }, Verified: { transitions: { reopen: { targetState: 'Open' } } } }, requirements: { Assignee: { type: entities.User.fieldType }, QAEngineer: { type: entities.User, login: 'qa.engineer' } } });

Start Work on Assignment

The second rule supports a pull-based queue. When an issue is Open and a developer assigns it to themselves, the rule moves the issue to In Progress.

const entities = require('@jetbrains/youtrack-scripting-api/entities'); exports.rule = entities.Issue.onChange({ title: 'Start work when a developer assigns an open issue to themselves', guard: (ctx) => { const issue = ctx.issue; return issue.isReported && issue.fields.is(ctx.State, ctx.State.Open) && issue.fields.isChanged(ctx.Assignee) && issue.fields.Assignee && issue.fields.Assignee.login === ctx.currentUser.login; }, action: (ctx) => { ctx.issue.fields.State = ctx.State.InProgress; }, requirements: { State: { type: entities.State.fieldType, Open: {}, InProgress: { name: 'In Progress' } }, Assignee: { type: entities.User.fieldType } } });
13 May 2026