llms.txt: machine-readable documentation index. Markdown version of this page is also available.

YouTrack Developer Portal Help

Marketplace Submission Checklist

You can develop apps for your own use or for use within your company. If you choose to publish your app on JetBrains Marketplace, use this checklist to prepare it for review.

The YouTrack team reviews every app submitted to JetBrains Marketplace before publication. This checklist describes the review criteria. Use it before submitting your app to identify issues that may delay approval.

Pre-submission Checklist

Use this summary before each Marketplace submission. You can also add these checks to your release checklist.

Listing

  • The listing includes a description and at least one in-app screenshot. Change notes are recommended.

  • If setup goes beyond installation, a Getting Started section provides instructions that match the current app version.

  • The product name is spelled "YouTrack" throughout the listing and interface.

  • The compatibility range is correct, with minYouTrackVersion set to 2026.2 or later if backend scripts make outbound HTTP calls or use async functions.

  • If the app sends data outside YouTrack, the listing discloses the processing and links to a privacy policy.

For details, see Marketplace Listing and UI and User Experience.

Package

  • Backend .js files are at the package root.

  • Paths in manifest.json match the ZIP contents.

  • settings.json saves without validation errors and contains no placeholder content.

  • Widget and endpoint permissions match the intended users.

  • The package declares no unused extension properties.

  • The package contains no obfuscated code, debug endpoints, development URLs, or internal files.

For details, see Package and Manifest.

Security

  • The package contains no hardcoded credentials.

  • All secrets use "format": "secret" in settings.json.

  • Extension properties contain no secrets, and no endpoint returns a secret.

  • Any leaked secrets have been revoked or rotated.

  • Every global or user endpoint declares permissions or checks access explicitly in code.

  • Write endpoints require write permissions.

  • Documented roles are enforced on the server.

  • The app checks the requester's permissions for entities referenced by supplied IDs.

  • Open endpoints don't expose license data, settings, or email addresses.

  • GET requests never write data.

  • Error responses contain no stack traces or raw exceptions.

  • Custom MCP tools receive the same access and input checks as HTTP handlers.

  • Endpoints have been tested with a low-privilege user's token.

For details, see Secrets and Credentials, HTTP Handlers and Access Control, and Test Endpoint Access.

Backend Behavior

  • All outbound HTTP calls from backend scripts use *Async methods with matching asyncFunctions handlers.

  • onSchedule rules use focused searches, leave enough time between runs, and finish within the default 3-minute limit.

  • onChange guards are precise and read-only, and no resource-intensive work runs in the transaction.

  • Extension property values are small and written infrequently. Data is stored per entity, or shared values are protected against concurrent updates.

For details, see Outbound HTTP Calls (Async Required), On-schedule Rules, On-change Rules, and Storing Data.

Frontend and UI

  • External requests and content delivery network (CDN) assets are essential to the app and disclosed in the listing.

  • Widgets don't use eval, new Function, or dynamic imports of remote code.

  • User content is sanitized before HTML rendering.

  • Links to YouTrack pages include contextPath.

  • Every REST call a widget makes works for the users who see it.

  • Widgets use Ring UI or match YouTrack's look and feel, fit their expected size, and work in the dark theme.

For details, see Frontend (Widget) Code and UI and User Experience.

Functional Quality

  • The app works for regular users.

  • Project-scoped widgets work after the app is attached to a project.

  • Configuration changes require the administrator's consent.

  • Updates preserve existing data and settings.

For details, see Functional Quality.

How the Review Works

The YouTrack team reviews the listing and code, installs the app, and tests its behavior. Each submission goes through the following checks:

Step

Review activity

Listing review

The team reviews the Marketplace listing: description, screenshots, Getting Started instructions, and change notes.

Code audit

The team downloads the submitted ZIP archive and reviews every widget bundle and backend script.

Installation

The team installs the app on a clean YouTrack Server and attaches it to a test project.

Functional testing

The team tests every advertised feature as an administrator and as a regular user.

UI check

The team checks every widget in light and dark themes.

Feedback

If changes are needed, you receive specific change requests. The app is published once it meets the review requirements.

Severity Levels

Each change request is marked with a severity:

Severity

Meaning

Must be fixed before approval?

Blocker

A security vulnerability, data exposure, risk to YouTrack's stability, or a core feature that doesn't work.

Yes.

Major

A significant problem, such as a missing check on a low-sensitivity endpoint or a broken secondary feature.

Yes, unless the currently published version has the same issue. In that case, fix it in the next version. Synchronous HTTP calls are an exception: they must always be fixed before approval (Outbound HTTP Calls (Async Required)).

Minor

A quality improvement or a low-risk issue.

No. A fix in the next version is recommended.

Ten Common Reasons Apps Are Sent Back

These issues come up often in reviews. Checking them first can save you another round of reviews.

  1. Tokens stored in cleartext. Declare them in settings.json with "format": "secret". Don't put them in extension properties. For details, see Secrets and Credentials.

  2. global or user endpoints without access checks. Declare permissions or check access explicitly in code. For details, see HTTP Handlers and Access Control.

  3. Access rules enforced only in the UI. Hiding a button doesn't stop someone from calling the endpoint directly. For details, see HTTP Handlers and Access Control.

  4. Trusting IDs from the request body. Accepting an entity ID from a request without checking the caller's access can expose data they aren't allowed to see. For details, see HTTP Handlers and Access Control.

  5. Synchronous outbound HTTP (postSync, getSync, and so on), especially calls to AI services, including large language model (LLM) APIs. Use the *Async methods instead. For details, see Outbound HTTP Calls (Async Required).

  6. Obfuscated code. The review team needs readable code to verify what the app does. For details, see Package and Manifest.

  7. Undisclosed calls to third-party servers, such as license servers, analytics, CDNs, or hosted editors. For details, see Marketplace Listing.

  8. Links that ignore the YouTrack context path. These break when YouTrack Server is hosted at /youtrack. For details, see Frontend (Widget) Code.

  9. Widgets that are unreadable in dark theme. Check every widget with YouTrack's dark theme turned on. For details, see UI and User Experience.

  10. Error responses that leak stack traces or internal details. Return a short message and log the details on the server. For details, see HTTP Handlers and Access Control.

Marketplace Listing

The listing is the first thing administrators see. The review team also starts with the listing.

  • Write a clear description. Explain what the app does, who it is for, and its main features.

  • Include at least one screenshot. Show the app in action inside YouTrack. Screenshots should show the key features, not just a logo or a settings form.

  • Add a Getting Started section if setup goes beyond installation. Explain any extra steps: configuration, permissions, attaching the app to projects, and setting up external accounts or tokens. Make sure the instructions match the current app version, including file names, menu names, and setting labels.

  • Add change notes for each version (recommended). Describe what actually changed. Make sure the submitted code includes the fixes you mention.

  • Include a vendor name. For details, see vendor in the manifest.

  • Set the correct compatibility range. If backend scripts make outbound HTTP calls or use async functions, set minYouTrackVersion in manifest.json to "2026.2" or later. This version requirement doesn't apply to requests that widgets make from the browser. For details, see the manifest schema and Outbound HTTP Calls (Async Required).

  • Disclose third-party data processing. If the app sends any data outside YouTrack, say so in the listing and link to a privacy policy. This applies to license checks, telemetry, user counts, hosted editors, AI services, and CDNs. Say what is sent, to whom, and why.

Package and Manifest

For the basic package layout, see Launch Checklist.

  • Backend scripts are at the package root. YouTrack registers HTTP handlers and workflow rules from .js files in the app's root folder. If they sit in a subfolder such as backend/, the handlers are never registered, and only the widgets work. For details, see App Package Overview.

  • File paths in manifest.json match the files in the ZIP package. Check indexPath, iconPath, and settingsSchemaPath. A wrong path can cause a broken icon or a missing settings dialog. For details, see widgets in the manifest.

  • settings.json is a valid schema for YouTrack. If you include "$schema", use http://json-schema.org/draft-07/schema#: the https:// form fails validation when the settings are saved. Open the settings in YouTrack and save them once to confirm. Remove leftover boilerplate, such as a demo $id or a placeholder title. For details, see App Settings.

  • Permission restrictions match the intended users. The permissions property doesn't grant permissions to the app. It limits who can see a widget or call an endpoint. An empty list means no restriction. Make sure each restriction matches the permissions needed for the action. For details, see App Permissions.

  • The app declares no unused extension properties. Remove properties the app declares but doesn't use. For details, see Extension Properties.

  • Backend scripts and widget bundles contain no obfuscated code. Standard bundler minification is fine. Output from tools like javascript-obfuscator (string arrays, self-defending wrappers, disabled console) is not.

  • License checks use readable code. If you need license enforcement, sign license data on your server and verify the signature in readable code.

  • Remove development leftovers and use production service URLs. Remove debug endpoints and panels, verbose logging of server responses, and internal documentation. Replace any staging, sandbox, or development URLs for license, payment, or API services with production URLs.

Secrets and Credentials

Check secret handling carefully before submission. These issues can block approval and may require several review cycles to resolve.

  • The package contains no hardcoded credentials. This includes API keys, bearer tokens, custom authentication headers, and YouTrack permanent tokens. Hardcoded credentials count as a Blocker issue, even if they're obfuscated or used only for a trial.

  • Tokens, passwords, and client secrets use "format": "secret" in settings.json.

    { "type": "object", "properties": { "apiToken": { "type": "string", "format": "secret", "title": "API token" } } }

    Secret values are masked (<***>) in the administration interface, in REST responses, and in console.log() output. A setting declared only as "type": "string" leaves its value readable in cleartext by administrators and through the REST API. Secret fields also work in arrays of objects. For details, see Working with Settings for Secrets.

  • Secrets are never stored in extension properties. Extension properties (AppGlobalStorage, User, Project, Issue) have no secret type. Their values are stored in cleartext. This covers OAuth access and refresh tokens, webhook secrets, and tokens copied from app settings. For details, see Extension Properties.

  • Custom encryption isn't a substitute for secret storage. If you use XOR or another reversible scheme and ship the key in the package, anyone with the ZIP can recover the secret. Use "format": "secret" in settings.json.

  • No endpoint returns a secret. This also applies to responses sent to the app's own widgets. If a widget needs an operation that uses a secret, run it in a backend handler and return only the result.

  • Keep service account permissions and token scopes to the minimum needed. If the app creates a service account or asks for a token, request only the services and permissions it needs. For example, don't grant access to the Hub (user management) service if the app only calls the YouTrack API.

  • The app reuses only accounts it created. Before reusing an existing service account, check that the app created it. One way is to store the account's ID when the app creates it and check that ID before reuse. Otherwise, the app may issue a token for an account controlled by someone else.

  • Leaked secrets are revoked or rotated. If a secret ever shipped in a package or was exposed through an endpoint, revoke or rotate it. Removing it from the next version isn't enough because old packages are still available.

HTTP Handlers and Access Control

YouTrack enforces visibility differently depending on endpoint scope. Make sure you understand which protections YouTrack provides automatically and which you need to add. For details, see Scopes for HTTP Handlers and the HTTP handler reference.

All endpoints are subject to app activation, handler visibility, and any declared permissions checks.

Scope

What YouTrack checks before your code runs

What you must add

issue, article, project

The caller can see the entity. If not, the response is 404.

Declare permissions for operations beyond reading, such as updates, deletion, or changes to settings.

global (the default), user

The scope adds no entity visibility check.

Always declare permissions, or check access in handle().

Access Control Checklist

  • Every global or user endpoint declares permissions or checks access explicitly in code.

    exports.httpHandler = { endpoints: [ { method: 'POST', path: 'config', scope: 'global', permissions: ['ADMIN_UPDATE_APP'], handle: (ctx) => { /* ... */ } } ] };

    List permission keys in an array. A nonempty array requires at least one listed permission. For details, see endpoint properties and permission keys.

  • Write endpoints require write permissions. An endpoint that changes data must not be callable with READ_ISSUE or READ_PROJECT alone. Project settings should require UPDATE_PROJECT. App-wide settings should require ADMIN_UPDATE_APP.

  • Role restrictions described in your app documentation are enforced on the server. If the listing says "only managers can edit" or "only app administrators can rename columns", the backend must check this. Hiding controls in the widget is not access control.

  • Never trust entity IDs from the request. If a handler loads Issue.findById(body.issueId) or a project from body.projectId, check the caller's permissions for that entity. An issue, article, or project endpoint checks visibility only for its scope entity. Check access separately for any other entity identified in the request, and add the permissions needed to change data. A common bug is a project endpoint that accepts another project's ID in the body.

  • Respect visibility when returning lists. Don't return all issues, attachments, or records in a project when the caller can only see some of them. Check per-item visibility, such as attachment visibility groups and issue visibility.

  • Open endpoints don't expose sensitive data. License data (license holder, user counts, and expiration details), app settings, administrators' email addresses, and YouTrack installation IDs must not be readable by regular users or guests.

  • GET requests have no side effects. Don't write data or change configuration in response to a read request or just because someone opens an administration page.

  • Validate input. Check the structure and data types of request bodies, limit their size, and reject unsupported values. Escape user content before you embed it in HTML, XML, or Markdown.

  • Return meaningful HTTP status codes. Set ctx.response.code = 403 (or 400, 404) explicitly. Don't send 200 with ok: false. Don't set a code and then throw, because the runtime turns that into a 500. For details, see response object.

  • Don't expose internal details in error responses. Return a short, user-facing message. Don't return e.stack, raw exception text, or file paths. Log details on the server instead.

  • Custom MCP tools validate access and input. An MCP tool is another way to reach your app's data. Check the caller's access, validate every argument (for example, that an issue reference exists and is visible to the caller), and don't return cached data the caller couldn't see otherwise. For details, see Custom MCP Tools.

Before submitting your app, test endpoint access with an account that has minimal permissions. The YouTrack team performs these checks during review.

Test Endpoint Access

  1. Create a test user with minimal permissions, for example a user who has only the Observer role.

  2. Call each endpoint directly using that user's token.

  3. Try reading another project's data, writing settings, and changing license state. Check that the endpoints reject operations the user isn't permitted to perform.

Outbound HTTP Calls (Async Required)

Marketplace apps must use async HTTP for all outbound calls from backend scripts. The backend *Async methods are available from YouTrack 2026.2, so if your backend calls external services, set minYouTrackVersion to "2026.2" or later. This doesn't apply to requests that widgets make from the browser.

For details about Asynchronous Functions, see supported script types, execution limits, sharing state, and HTTP calls and chaining. For the HTTP methods, see Send Requests after Transaction Completion.

Synchronous calls keep the current transaction or request thread open while waiting for a response. The synchronous methods are doSync, getSync, postSync, putSync, patchSync, deleteSync, and headSync. Use the corresponding *Async methods. For HEAD requests, use doAsync('HEAD', ...).

  • In onChange and action rules, they keep the write transaction open and can block other users who are updating the same entities.

  • In onSchedule rules, they count toward the default 3-minute execution limit (see Workflow Rules and Performance).

  • In HTTP handlers, they hold a request thread.

Async functions are supported in onChange, action, and onSchedule rules, HTTP handlers, and custom MCP tools.

If more than one condition in the following table applies, use the highest severity.

Issue

Severity

A *Sync call to an AI/LLM service (OpenAI, Anthropic, Gemini, OpenRouter, any /chat/completions-style API).

Blocker

A *Sync call inside onChange or action.

Blocker

A *Sync call in onSchedule, an HTTP handler, or an MCP tool.

Major

*Async used, but the response is handled incorrectly.

Major

Example: An Async HTTP Call from an On-change Rule

const entities = require('@jetbrains/youtrack-scripting-api/entities'); const http = require('@jetbrains/youtrack-scripting-api/http'); exports.rule = entities.Issue.onChange({ title: 'Notify external service', guard: (ctx) => ctx.issue.fields.isChanged(ctx.State), action: (ctx) => { // The handler runs later, in a new transaction. Pass what it needs via ctx.store(). // An entity is stored as a reference: on load you get its current state, not a snapshot. ctx.store('issue', ctx.issue); const state = ctx.issue.fields.State; const conn = new http.Connection('https://api.example.com'); conn.addHeader('Content-Type', 'application/json'); conn.postAsync('/webhook', null, JSON.stringify({ state: state ? state.name : null }), 'onResponse'); }, asyncFunctions: { onResponse: (ctx) => { const issue = ctx.load('issue'); if (ctx.response.isSuccess) { const data = ctx.response.json(); // Update `issue` using `data` here. } else { console.warn('Webhook failed with status', ctx.response.code); } } }, requirements: { State: { type: entities.State.fieldType } } });

Async Checklist

  • The handler name matches the key in asyncFunctions. The string passed to *Async(..., 'onResponse') or ctx.invokeAsync('name') must be a key in asyncFunctions of the same rule or handler. ctx.invokeAsync() throws an error if the handler name doesn't match. For an HTTP response handler, a mismatch only produces a warning in the app log, and the response is never processed. Inline callbacks aren't supported.

  • asyncFunctions is in the right place. In a workflow rule or MCP tool, put it at the same object level as action or execute. In an HTTP handler, put it on exports.httpHandler at the same level as endpoints, outside individual endpoints.

  • Each execution schedules at most one async call. A function can schedule either one ctx.invokeAsync() call or one *Async HTTP request. A second call throws an error.

  • Chains stay within 10 steps (the default limit), and every self-scheduling chain has a stop condition.

  • Don't use the return value of an async HTTP method. *Async returns nothing. Read the result from ctx.response inside the handler.

  • Pass state only through ctx.store()/ctx.load(). Only primitives, null, and entity references are supported. Convert objects and arrays to JSON strings with JSON.stringify() before storing them.

  • Don't rely on the transaction state in the handler. isChanged() and oldValue() don't work there. Capture any values you need from them before scheduling the async call.

  • Check ctx.response.isSuccess and handle failures. Never throw errors. An unhandled error stops the chain. Let users know when a call fails, especially if a service rejects it because of a rate limit.

  • minYouTrackVersion is 2026.2 or later in manifest.json.

Workflow Rules and Performance

Your app runs inside the customer's YouTrack. Slow rules can make YouTrack less responsive for other users.

On-schedule Rules

For details, see On-schedule Rules.

Cron expressions use the Java Quartz format (6 fields: sec min hour day month weekday). 0 * * * * ? means every minute, not every second.

  • Narrow search queries. The rule runs once for every issue the query matches. An empty query or #Unresolved in YouTrack can match tens of thousands of issues. Narrow it down with a project, a tag, or a date range such as updated: {Last week}.

  • Leave enough time between runs. If a run hasn't finished when the next one is due, the next run is skipped, not queued.

  • Keep each run below the default 3-minute limit. If a run reaches the limit, YouTrack stops it and skips the remaining issues.

  • Handle per-issue errors. Each issue runs in its own transaction. A failure for one issue doesn't stop the others and is recorded only in the workflow log. Make sure users also get feedback about failures they need to act on.

  • search only selects issues. If search is a function, it should only build the query. Don't send requests or write data from it.

On-change Rules

For details, see On-change Rules.

  • Keep resource-intensive work out of the transaction. Large searches and outbound HTTP calls might freeze the transaction for all users. Use async functions instead.

  • Use a precise guard. If the guard always returns true, the rule runs on every save.

  • Keep guard free of side effects. Guards must only read data.

  • Avoid cycles between rules. A change made by a rule can trigger on-change rules again. If repeated changes exceed the iteration limit, YouTrack rejects the whole transaction with a workflow-cycle error, and the user's change isn't saved.

General Performance Checks

  • Keep loops and data fetches bounded. Don't load every user just to count them or page through every issue on each request. Set a limit on the number of results.

  • Limit parallel requests from widgets. Dozens of REST API calls at once when a widget loads can slow down YouTrack.

Storing Data

Use extension properties for small amounts of app data. Keep values small, avoid frequent writes, and plan for concurrent updates. For details, see App Global Storage.

  • No secrets in extension properties. For details, see Secrets and Credentials.

  • Avoid frequent writes. Each write updates the YouTrack database. Writing a property for every matching issue in a frequently scheduled onSchedule rule can put extra load on YouTrack.

  • Keep values small. Don't store logs, large JSON blobs, or whole datasets. Cap the size of anything users can submit.

  • Plan for concurrent edits. Concurrent updates to the same extension property can overwrite each other. If two users save the same JSON blob, the last write wins. Store data per entity rather than in one global blob, or add a revision check.

  • Keep user data private. Keep each user's settings separate, and return only the settings the caller is allowed to see. Don't keep every user's settings in one shared global blob that every handler reads and returns.

  • Use settings for configuration. For administrator-configured values, settings.json (global or project scope) gives you validation, a UI, and secret masking. For details, see App Settings.

Frontend (Widget) Code

Widgets run in sandboxed iframes. The review covers every bundle, including minified ones.

  • External requests are essential to the app and disclosed in the listing. This includes CDN stylesheets and scripts. Bundle Ring UI and other libraries instead of loading them from unpkg or similar at runtime.

  • Widgets don't evaluate strings as code or dynamically import remote code. Don't use eval, new Function, or dynamic import() of remote code.

  • The app doesn't generate undisclosed background traffic. Don't use sendBeacon, WebSocket connections to external hosts, tracking pixels, or document.cookie unless they are essential and disclosed.

  • The app doesn't use long encoded strings to hide URLs, code, or credentials. Check long base64-encoded, hexadecimal, and similarly encoded strings.

  • postMessage targets a specific origin, not '*', when it carries data such as OAuth codes.

  • User content is sanitized before it reaches innerHTML or dangerouslySetInnerHTML. This includes Markdown rendered with HTML enabled and document previews. Block javascript: links.

  • The widget doesn't cache sensitive data in localStorage. Issue summaries and descriptions cached in the browser can remain accessible after the user loses access to those issues.

  • Links respect the context path. Self-hosted YouTrack can run under a path such as https://host/youtrack. Root-absolute links starting with /, such as /issue/ABC-1, skip that context path. Relative links can also point to the wrong place because the widget's base URL is the app's resource folder. Fetch the context path and build your links from it:

    const host = await YTApp.register(); const { contextPath } = await host.fetchYouTrack('config?fields=contextPath'); // contextPath is the configured path prefix, such as "/youtrack", or "" at the root URL. const issueUrl = `${contextPath}/issue/${issueId}`;

    Set target="_top" on these links so they open in the YouTrack page, outside the widget iframe. For example: <a href={issueUrl} target="_top">. For details, see the YTApp object and the fetchYouTrack method.

  • Widget requests use the scope and entity ID required by the endpoint. For example, an issue-menu widget must not use an issue ID where a project ID is expected. A widget can call a global endpoint regardless of the widget's scope. For details, see HTTP endpoint scopes.

  • Every REST call a widget makes works for the users who see it. Some endpoints and fields return 403 for regular users, for example /api/groups?fields=users. You can't tell from the path alone: some /api/admin/... endpoints work for regular users. Test each endpoint and field set with an account that has only the permissions your widget's intended users need. If an operation needs more access, put it in a backend handler that checks whether the caller is allowed to perform it.

UI and User Experience

Apps should feel like part of YouTrack.

  • Use Ring UI or match YouTrack's look and feel. Use Ring UI (@jetbrains/ring-ui) for buttons, inputs, loading indicators, alerts, and dialogs. If you don't use it, match YouTrack's colors, spacing, and fonts. For details, see Ring UI.

  • Widgets work with the dark theme enabled. Switch YouTrack to the dark theme (click your avatar, then select Appearance > Dark) and check every widget: text, backgrounds, dialogs, and disabled states. Follow YouTrack's theme, not the operating system's prefers-color-scheme setting.

  • Widgets fit their intended size. Check that content isn't clipped, no inner scrollbars appear at the default size, and there are no large empty areas. When specifying dimensions in the manifest, use defaultDimensions for dashboard and Markdown widgets and expectedDimensions for other supported extension points. For details, see widget size and widget properties in the manifest.

  • Widgets use the system font stack. Avoid decorative fonts.

  • Write the product name as "YouTrack" throughout the listing, UI text, setting labels, dialogs, and error messages. Don't use Youtrack, youtrack, or YOUTRACK.

  • Give empty values meaningful labels. Don't display [object Object], undefined, or ?. Use YouTrack's wording, such as "Unassigned" or "No value," where appropriate.

  • Show widgets only to users with the required permissions. If a menu item would lead to an access error, hide it with widget permissions or a guard. For details, see App Permissions and Conditional Widgets.

  • The interface is localized consistently. Don't mix languages in your interface text, and make sure every translation key exists for every language your app supports.

  • Field values and labels created by the app contain no spelling mistakes. These values become part of customers' data.

Functional Quality

The review covers every feature described in your listing. Check the following before submission:

  • Every advertised feature works on a clean YouTrack Server. Test outside your development environment.

  • Project-scoped widgets work after the app is attached to a project. Issue, article, project-settings, and helpdesk widgets appear only in projects the app is attached to. Test this and explain the attachment step in Getting Started. For details, see Extension Points for Widgets.

  • The app works for regular users. Test with an account that has only the permissions the app's intended users need.

  • Built-in groups work in permission checks. Check membership with user.isInGroup(groupName) rather than searching user.groups, which may not include built-in groups such as "All Users" or "Registered Users". For details, see User.isInGroup.

  • The app handles edge cases. Test empty or optional fields, missing work item types, special characters in user input and queries (&, #, +), and URLs with query strings.

  • Multi-step operations don't lose or duplicate data. If an operation has several steps, such as copying and then deleting an item, order the steps or roll them back so a failure halfway through doesn't cause data loss or duplication.

  • The app makes configuration changes only with the administrator's consent. Opening an administration page must not create fields, change colors, or edit bundles. Ask the administrator to confirm these changes before applying them.

  • Updates keep existing data and settings. Check that the updated app can still read and use data and settings from the previous version.

06 November 2026