# Marketplace Submission Checklist

> For the complete documentation index, see [llms.txt](https://www.jetbrains.com/help/youtrack/devportal/llms.txt).

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](#marketplace-listing) and [UI and User Experience](#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](#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](#secrets-and-credentials), [HTTP Handlers and Access Control](#http-handlers-and-access-control), and [Test Endpoint Access](#test-it-yourself).

### 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)](#outbound-http-calls-async-required), [On-schedule Rules](#onschedule-rules), [On-change Rules](#onchange-rules), and [Storing Data](#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](#frontend-widget-code) and [UI and User Experience](#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](#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)](#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](#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](#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](#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](#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)](#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](#package-and-manifest).

7. Undisclosed calls to third-party servers, such as license servers, analytics, CDNs, or hosted editors. For details, see [Marketplace Listing](#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](#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](#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](#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](app-manifest.html#app-manifest-vendor).

* 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](app-manifest.html#app-manifest-schema) and [Outbound HTTP Calls (Async Required)](#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](app-launch-checklist.html).

* 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](app-overview.html#app-package).

* 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](app-manifest.html#app-manifest-widgets).

* `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](app-settings.html#define-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](app-permissions.html).

* The app declares no unused extension properties. Remove properties the app declares but doesn't use. For details, see [Extension Properties](apps-extension-properties.html).

* 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`. ```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](app-settings.html#working-with-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](apps-extension-properties.html).

* 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](apps-global-project-level.html#scopes-http-handlers) and the [HTTP handler reference](apps-reference-http-handlers.html#scope).

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. ```JAVASCRIPT 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](apps-reference-http-handlers.html#endpoints) and [permission keys](app-permissions.html).

* 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](apps-reference-http-handlers.html#response).

* 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](custom-ai-tools.html).

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

Procedure: 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](async-functions.html), see [supported script types](async-functions.html#supported-script-types), [execution limits](async-functions.html#execution-limits), [sharing state](async-functions.html#share-state-across-async-calls), and [HTTP calls and chaining](async-functions.html#async-http-calls-and-chaining). For the HTTP methods, see [Send Requests after Transaction Completion](JS-Workflow-REST-API.html#workflow-rest-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](#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](custom-ai-tools.html).

> **Warning:**
> Any `*Sync` call must be fixed before approval, even if the published version already has it. The severity depends on where the call runs and which service it uses.

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

```JAVASCRIPT
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`.

> **Note:**
> The published TypeScript type definitions in the `@jetbrains/youtrack-scripting-api` npm package (2022.1.x) don't include the `*Async` methods. The YouTrack runtime provides them from version 2026.2.

## 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](on-schedule-rules.html).

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](on-change-rules.html).

* 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](apps-extension-properties.html#global-storage).

* No secrets in extension properties. For details, see [Secrets and Credentials](#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](app-settings.html).

## 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: ```JAVASCRIPT 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](apps-host-api.html#ytapp-object) and the [fetchYouTrack](apps-host-api.html#fetch-youtrack) 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](apps-reference-http-handlers.html#scope).

* 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](https://jetbrains.github.io/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](apps-widgets.html#widget-size) and [widget properties in the manifest](app-manifest.html#app-manifest-widgets).

* 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](app-permissions.html) and [Conditional Widgets](apps-widget-guards.html).

* 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](apps-reference-extension-points.html).

* 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](v1-User.html#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.

