A YouTrack app that locks resolved issues and allows users to freeze articles. A locked issue or a frozen article is read-only.
The app locks an issue when the issue becomes resolved. A locked issue is read-only. The rule rejects each change to a locked issue and shows a message. The user keeps no change.
The one permitted change is to reopen the issue.
A project admin sets the behaviour for each project. Open the settings of the project, then open the Lock tab, section Issues. These settings are available:
| Setting | Default | Description |
|---|---|---|
| Lock a resolved issue | On | Turns the lock on or off for issues in the project. |
| Who can reopen a locked issue | Anyone with update access | Select Anyone with update access, The reporter, The user who resolved the issue, The reporter or the user who resolved the issue, or Project admins only. A project admin can always reopen an issue. |
| Comments | Permitted | Lets a user add, edit, or remove a comment on a locked issue. |
| Links | Permitted | Lets a user add or remove a link on a locked issue. |
| Work items | Permitted | Lets a user log time on a locked issue. |
| Attachments | Not permitted | Lets a user add or remove an attachment on a locked issue. |
| Tags | Not permitted | Lets a user add or remove a tag on a locked issue. |
| Deletion | Not permitted | Lets a user delete a locked issue. |
The app rejects each change that is not in the list above. This includes a custom field, the summary, the description, and the visibility.
A resolved issue shows a Lock status line at the end of its field panel: a lock icon and "Issue resolved and locked by .", or "Issue locks are disabled in this project." when the lock for issues is off. The line reads "Issue resolved and locked." when the app does not know who resolved the issue. Click the information icon to see what the settings permit on the issue, and who can reopen it. An unresolved issue does not show the line.
YouTrack does not record who resolved an issue, so the app does. The rule Record who resolved an
issue sets two extension properties of the issue when it becomes resolved: resolvedBy (the
user) and resolvedAt (milliseconds since 1970-01-01T00:00Z). The rule runs also when the lock for
issues is off, so that the data exists if an admin turns the lock on later.
- A change from one resolved state to a different resolved state (for example,
FixedtoWon't fix) keeps the issue resolved. The issue stays locked, so the rule rejects the change. Reopen the issue, then resolve it again. - A project admin has no special permission to edit a locked issue. A project admin must also reopen the issue first.
- A reopen can also carry a change that the settings permit, for example a comment. It cannot carry a change to a field, the summary, or the description.
- An issue that became resolved before the app was installed has no recorded resolver. Under
The user who resolved the issueonly a project admin can reopen it. UnderThe reporter or the user who resolved the issuethe reporter or a project admin can. - A reopen does not clear
resolvedByandresolvedAt. The next resolution overwrites them. - A locked issue cannot be deleted unless Deletion is permitted. Reopen the issue, then delete it.
A user freezes an article to lock it. A frozen article is read-only. A user unfreezes the article to edit it again. An article that is not frozen is editable*.
The author is the user who created the article. The user who froze the article is the user who last made it frozen.
A status line above the activity stream of the article shows the state: "Editable. Freeze article.", or "Frozen by . Unfreeze to update.". Click "Freeze article", "Unfreeze", or the state icon to freeze or unfreeze. The widget asks you to confirm. Click the information icon to see what the settings permit on the article, and who can unfreeze it.
A project admin sets the behaviour for each project. Open the settings of the project, then open the Lock tab, section Articles. These settings are available:
| Setting | Default | Description |
|---|---|---|
| Make a frozen article read-only | On | Turns the lock on or off for articles in the project. |
| Who can unfreeze a frozen article | Anyone with edit access | See the table below. |
| Comments | Permitted | Lets a user add, edit, or remove a comment on a frozen article. |
| Child articles | Permitted | Lets a user add or remove a child article of a frozen article. |
| Tags | Permitted | Lets a user add or remove a tag on a frozen article. |
| Attachments | Not permitted | Lets a user add or remove an attachment on a frozen article. |
| Deletion | Not permitted | Lets a user delete a frozen article. |
The app rejects each other change to a frozen article: the title, the content and the visibility. A user can always move a frozen article to a different parent article.
A user can freeze an article only if the same user can also unfreeze it. Nobody can lock themselves out. A project admin can always freeze and unfreeze.
Edit access means what YouTrack means by it: a project admin, a user with the Update Article permission in the project, or the author of the article with the Create Article permission. An author who can edit only their own articles can freeze and unfreeze those articles.
| Who can unfreeze | Project admin | Author | Other editor |
|---|---|---|---|
| Anyone with edit access | Freeze and unfreeze | Freeze and unfreeze | Freeze and unfreeze |
| The author | Freeze and unfreeze | Freeze and unfreeze | Neither |
| The user who froze the article | Freeze and unfreeze | Freeze. Unfreeze only as the user who froze it | Freeze. Unfreeze only as the user who froze it |
| The author or the user who froze the article | Freeze and unfreeze | Freeze and unfreeze | Freeze. Unfreeze only as the user who froze it |
| Project admins only | Freeze and unfreeze | Neither | Neither |
- An edit survives a block. You edit a frozen article in an editor that saves at the end. The rule runs at that save. The message appears then, and your text stays in the editor. Unfreeze the article, then save again.
- A block on a frozen parent cancels the operation on the child. If child articles are not permitted, the app rejects the creation, the move, or the deletion of a child article. The message names the parent article.
- The handler checks the project permission, not the article visibility. A user who can update articles in the project, but who cannot see this article, can still freeze it through the endpoint if the user knows the ID. The endpoint itself requires only the Read Article permission, because YouTrack cannot express "the author of this article" as a static permission. The handler refuses a user without edit access before it writes anything.
- The lock off leaves a frozen article frozen. Unfreeze still works. Nothing else does.
- Two admins who save two settings sections at the same moment write the same string. The window is milliseconds.
- A frozen article cannot be deleted unless Deletion is permitted. Unfreeze the article, then delete it. YouTrack deletes the child articles with the parent, and the rule of each frozen child runs too, so a frozen child also stops the deletion of its parent.
- A move to a different project, the order of the rules, a bulk operation and an integration account: the same limits as for issues.
- A draft that already has a resolved state cannot be discarded while the lock is on. YouTrack
clears
isReportedwhen it logically deletes an issue, so the rule cannot tell a discarded draft from a deleted issue. Set the state of the draft to an unresolved value, then discard it. - The app does not control a move to a different project. The rule ignores a save that changes the project. YouTrack uses the rules of the destination project for a move. That project can have no lock, so the app cannot stop a move out of a locked project. The app therefore also permits a move into a locked project. Use the project permissions to control a move.
- The order of the rules is not defined. If a different rule changes the same issue in the same save, its changes can appear as changes of the user. This can cause a wrong block, or let a change through.
- A link change goes to the two issues. If a user links an open issue to a locked issue, the rule runs on the locked issue. A block then stops the save of the open issue. Links stay permitted by default, which prevents this.
- The spent time field is an exception. YouTrack calculates the spent time from the work items. When the settings permit a work item, the rule also permits each period field in that same save. A project that has a second period field, for example an estimation, can thus get a change to that field together with a work item.
- An integration account has no exemption. A VCS commit command, a helpdesk email answer, or a chat integration that writes to a locked issue gets the same block. To stop the lock, turn off Lock a resolved issue for the project.
- Install dependencies:
npm install- Create
.envfile in project root:
YOUTRACK_HOST=https://your-youtrack.url
YOUTRACK_TOKEN=perm:your-permanent-tokenGet a permanent token: YouTrack profile → Account Security → New token. See token management.
- Start development with watch mode:
npm run watchThis watches for changes and automatically uploads to YouTrack. No auto-reload, requires manual refresh.
src/
├── api/ # Generated API client and types
│ ├── index.ts # Type-safe API client
│ ├── youtrack-types.d.ts # YouTrack entity type shortcuts
│ ├── api.d.ts # Generated route types (auto-generated)
│ ├── api.zod.ts # Generated Zod schemas (auto-generated)
│ ├── app.d.ts # Generated app settings types (auto-generated)
│ └── extended-entities.d.ts # Generated extension property types (auto-generated)
├── backend/
│ ├── router/ # File-based API routes
│ │ ├── project/issueSettings/ # Reads and keeps the issue settings of a project
│ │ ├── project/articleSettings/ # Reads and keeps the article settings of a project
│ │ ├── article/lock/ # Reads the freeze state; freezes and unfreezes
│ │ └── issue/lock/ # Reads the lock state of an issue
│ ├── shared/
│ │ ├── lock-settings.ts # The settings model, the checks and the defaults
│ │ ├── permissions.ts # Who is a project admin; who can freeze, unfreeze, reopen
│ │ ├── article-lock.ts # The freeze state and the freeze and unfreeze operations
│ │ └── issue-lock.ts # The lock state of an issue; who resolved it; who can reopen
│ ├── types/ # Backend type definitions
│ │ ├── backend.global.d.ts # Global backend types and context types
│ │ └── utility.d.ts # Utility types for RPC extraction
│ └── requirements.ts # YouTrack fields and values that your app needs
├── workflows/
│ ├── lock-resolved-issue.ts # The rule that locks a resolved issue
│ ├── record-issue-resolver.ts # The rule that records who resolved an issue, and when
│ └── lock-frozen-article.ts # The rule that locks a frozen article
├── common/
│ └── utils/
│ └── logger.ts # Logger utility for frontend components
├── widgets/
│ ├── project-lock-config/ # The Lock tab in the settings of the project
│ ├── article-status/ # The status line above the activity stream of an article
│ ├── issue-status/ # The status line in the field panel of a resolved issue
│ └── shared/ # The dialog and the frame logic that the status lines share
├── entity-extensions.json # Declares the extension properties of a project, an issue and an article
└── app-id.ts # App identifier
npm run hmr- Start Vite dev server for hot reload (frontend only, port 9000)npm run watch- Watch mode with automatic rebuild and upload (recommended)npm run dev- Watch mode with hot reload enabled (fastest for frontend development)
npm run build- Full production build (backend → lint → frontend → validate)npm run build:nolint- Build without linting (faster for testing)npm run build:backend- Build backend only (generates API types)npm run build:frontend- Build frontend only (requires backend types)npm run clean- Remove generated API files
npm run upload-local- Upload using.envcredentialsnpm run update- Quick build + uploadnpm run dev:upload- Build and upload dev-mode bundle (for hot reload setup)
npm run g -- http-handler add --scope <scope> --path <path>- Generate a new HTTP handler (e.g.npm run g -- http-handler add --scope project --path settings)npm run g -- extension-property add --entity <Entity> --name <field>- Generate a new entity extension property (e.g.npm run g -- extension-property add --entity Issue --name myField)npm run g -- settings add --name <key> --type <type>- Add an app settings field
npm run lint- Run ESLintnpm run pack- Create distributable ZIP file