Developer Portal for YouTrack and Hub Help

Enhanced DX for TypeScript Apps

This guide describes the experimental enhanced developer experience, or Enhanced DX, for building YouTrack apps with TypeScript. Enhanced DX adds file-based routing, generated frontend API types, runtime validation in development mode, and a faster development loop on top of the standard app tooling.

Use this guide to create apps with the experimental TypeScript toolchain. To create a standard JavaScript app, start with the App Quick Start Guide.

With Enhanced DX, you still upload a regular app bundle to YouTrack. However, you author backend handlers in TypeScript and let the toolchain build them for you.

Enhanced DX Benefits

Compared with the standard app scaffolding flow, the Enhanced DX toolchain provides the following additions:

Standard app scaffolding

Enhanced DX scaffolding

JavaScript backend handlers

TypeScript backend handlers with type checking

Manual HTTP handler wiring

File-based routing for backend endpoints

No shared request or response contracts

Generated types for frontend API calls

Manual upload cycle

Automatic rebuild and upload in watch mode

Manual setup for handlers, settings, and properties

npm run g generator commands for common tasks

Frontend rebuild and upload on every change

Optional Vite support for frontend changes that appear immediately in npm run dev (hot module replacement)

Prerequisites

To create a YouTrack app with the Enhanced DX toolchain, you need:

  • Node.js version 18.20.4 or later installed on your machine

  • Access to a YouTrack instance where you can upload apps

  • A permanent token with Update Project permission

Create an App Package

To create an app with the Enhanced DX toolchain, use the standard app generator and select the TypeScript development approach.

To create an app package with Enhanced DX:

  1. Prepare an empty directory for your app.

  2. Run the app generator:

    npm create @jetbrains/youtrack-app
  3. When prompted for the development approach, choose TypeScript (Enhanced DX with file-based routing).

  4. Navigate to the generated app package directory.

  5. Create a .env file in the root directory of the app package:

    YOUTRACK_HOST=https://your-youtrack.example.com YOUTRACK_TOKEN=perm:your-permanent-token

As a result, you have a generated app package that is configured for local development and for uploading the app to your YouTrack instance by using the values stored in the .env file.

To start local development, run the following command. It builds the app and uploads a development version to YouTrack.

npm run dev

Work with the App Package

The generated app package includes separate commands for building the frontend and backend, developing the app locally, and uploading it to YouTrack.

Goal

Command

Develop locally with immediate frontend updates

npm run dev

Run watch mode with rebuild and upload

npm run watch

Generate backend types only

npm run build:backend

Build the frontend bundle

npm run build:frontend

Create a production build

npm run build

Upload the current build with credentials from .env

npm run upload-local

Build and upload the app

npm run update

Build Sequence

The frontend depends on API type definitions that are generated during the backend build. For this reason, when you work with the app package for the first time, you must build the backend before you build the frontend.

npm run build:backend npm run build:frontend

The generated files in src/api/ are overwritten during backend builds. Do not edit them manually.

Local Development

When you run npm run dev, the app package is prepared for local development as follows:

  • Previously generated API files are removed

  • The backend is built and the API type definitions are generated

  • A development version of the app is uploaded to YouTrack

  • A local development server is started on port 9000

  • Changes in backend files trigger a rebuild and a new upload when needed

  • Changes in frontend files appear immediately during development

Use npm run watch when you do not want to start the local development server. In that mode, frontend changes trigger a regular rebuild and upload instead of appearing immediately.

Package Structure

The generated app package contains the following directories and files. Some files in src/api/ are generated only after you build the backend.

src/ |- api/ | |- index.ts | |- youtrack-types.d.ts | |- api.d.ts # generated by the backend build | |- api.zod.ts # generated by the backend build | |- app.d.ts # generated by the backend build | `- extended-entities.d.ts # generated by the backend build |- backend/ | |- router/ | | |- global/ | | |- project/ | | |- issue/ | |- types/ | | |- backend.global.d.ts | | `- utility.d.ts | `- requirements.ts |- common/ | `- utils/ | `- logger.ts |- widgets/ `- app-id.ts

The generated files under src/api/ are derived from backend handlers, settings, and extension properties. The files in src/backend/router/ define the app's HTTP endpoints.

The initial app package contains sample handlers for global, project, and issue scopes. You can also add handlers for article and user scopes.

File-Based Routing

With file-based routing, the location and name of a backend handler file determine the endpoint that it provides. Instead of declaring endpoints manually in a separate configuration, you place each handler in a specific directory and name the file according to the supported HTTP method.

With Enhanced DX, backend handlers use the following path pattern:

src/backend/router/{scope}/{path}/{METHOD}.ts

Supported scopes are global, project, issue, article, and user. Route paths can be nested and may include hyphenated segments.

Handler Format

Every handler file exports the request type, response type, default handler, and a Handle alias for type generation.

/** * @zod-to-schema */ export type ProjectSettingsReq = { projectId: string; }; /** * @zod-to-schema */ export type ProjectSettingsRes = { name: string; shortName: string; }; export default function handle(ctx: CtxGet<ProjectSettingsRes, ProjectSettingsReq, "project">): void { ctx.response.json({ name: ctx.project.name, shortName: ctx.project.shortName, }); } export type Handle = typeof handle;

Only types annotated with @zod-to-schema are picked up for generated API contracts and runtime validation.

Context Types

Each handler receives a context object that provides access to the request, response, current user, app settings, and scope-specific YouTrack entities.

The following TypeScript helper types define the context object for handlers with different HTTP methods:

CtxGet<Res, Req?, Scope?> CtxDelete<Res, Req?, Scope?> CtxPost<Body, Res, Query?, Scope?> CtxPut<Body, Res, Query?, Scope?>

Each context always includes ctx.currentUser, ctx.settings, ctx.request, and ctx.response. Scope-specific properties such as ctx.issue or ctx.project are available when the handler directory and generic scope match.

Permissions

To define which permissions are required to access a handler, use the withPermissions helper function from the Enhanced DX runtime package.

import { withPermissions } from '@jetbrains/youtrack-enhanced-dx-tools/runtime'; function handle(ctx: CtxPost<Req, Res, "issue">): void { ctx.response.json({ ok: true }); } export default withPermissions(handle, ['read-issue', 'update-issue']); export type Handle = typeof handle;

Add App Elements

The generated app includes the npm run generate command and its shorthand alias npm run g. Use these commands to add widgets, handlers, extension properties, and settings.

Widget

Use the generator to add a widget for a selected extension point and update the app configuration automatically.

npm run g -- widget --key my-panel --extension-point ISSUE_BELOW_SUMMARY npm run g -- widget --key admin-page --extension-point MAIN_MENU_ITEM --name "Admin Page" npm run g -- widget --key project-tab --extension-point PROJECT_SETTINGS --permissions read-project
Handler

Use the generator to create backend handlers in the correct location and with the required type declarations.

npm run g -- handler global/health npm run g -- handler project/users --method POST npm run g -- h issue/comments --method POST --permissions read-issue,update-issue
Extension property

Use the generator to declare extension properties for YouTrack entities.

npm run g -- property Issue.customStatus npm run g -- property Issue.tags --type string --set npm run g -- p Article.rating --type integer

After rebuilding the backend, generated extension properties are available through ctx.issue.extensionProperties, ctx.project.extensionProperties, and related scoped entities.

App settings

Use the generator to initialize the settings schema and add properties to it.

npm run g -- settings init --title "My App Settings" --description "Admin configuration" npm run g -- settings add --name apiKey --type string --write-only npm run g -- settings add --name maxItems --type integer --min 1 --max 100

After you add or change backend elements, rebuild the backend or keep npm run dev running so generated files and uploaded app bundles stay up to date.

Use the Generated API Client

Frontend widgets can call backend handlers through a generated API client. This client is based on the handler files in src/backend/router/ and the request and response types declared in those files.

When you build the backend, the toolchain generates the ApiRouter type in src/api/api.d.ts. Widgets pass this type to the createApi helper function to get typed methods for each backend route.

import { createApi } from "@/api"; import type { ApiRouter } from "@/api/api"; const host = await YTApp.register(); const api = createApi<ApiRouter>(host); const settings = await api.project.settings.GET({ projectId: "ABC" }); const echo = await api.global.echo.POST({ message: "hello" }); const details = await api.issue.details.GET({ issueId: "DEMO-1" });

The structure of the api object follows the file-based route structure. For example, a handler in src/backend/router/project/settings/GET.ts is available as api.project.settings.GET(...).

For project-scoped and issue-scoped handlers, include the corresponding entity ID in the request data. When the request contains projectId, the client routes the call through the project endpoint. When the request contains issueId, the client routes the call through the issue endpoint.

If a route is missing from the generated api object, rebuild the backend to refresh src/api/api.d.ts.

Update Entity Fields

Backend handlers can update values on YouTrack entities, such as issues and projects. The way you update a value depends on whether the value is an extension property declared by the app or a regular YouTrack field.

Use POST or PUT handlers for updates that persist changes in YouTrack. Mutations in GET or DELETE handlers can fail at runtime.

Use the following approaches when you need to persist field changes from a handler:

Field

Update method

Extension properties declared by the app

Assign the value directly through ctx.issue.extensionProperties or the matching scoped entity

Regular issue or project fields, including custom fields

Use the set() helper function from @jetbrains/youtrack-enhanced-dx-tools/runtime

Here is an example that uses both approaches in an issue handler:

import { set } from '@jetbrains/youtrack-enhanced-dx-tools/runtime'; ctx.issue.extensionProperties.customNote = 'value'; set(ctx.issue, 'summary', 'New title'); set(ctx.issue, 'State', stateObject); set(ctx.issue, 'MyCustomField', 'value');

Troubleshooting

If the generated app package does not build, upload, or update in YouTrack as expected, use the following notes to check the most common setup and development issues.

Condition - The frontend build reports that it cannot find ./api/api.

Cause

Solution

The API type definitions have not been generated yet.

Run npm run build:backend to generate the files in src/api/.

Condition - Upload returns 401 Unauthorized.

Cause

Solution

The upload command cannot authenticate with YouTrack.

Check the values in .env. Make sure YOUTRACK_HOST includes https:// and YOUTRACK_TOKEN starts with perm:.

Condition - Changes are not uploading in watch mode.

Cause

Solution

The changed file is outside the watched source directory, or the watcher has stopped responding.

Verify that the changed file is inside src/. Restart npm run watch if the watcher becomes stuck.

Condition - The frontend is blank in local development mode.

Cause

Solution

The local development server is not running, or the frontend has a runtime error.

Make sure the local development server started on port 9000 and check the browser console for frontend errors.

Condition - ts-to-zod failed or was skipped.

Cause

Solution

The ts-to-zod package is not available in the app package. This can happen when dependencies were not installed correctly or the app package was modified manually.

Run npm install. If the package is still missing, install the development dependency with npm install -D ts-to-zod. Without it, runtime validation is disabled even though the build can still complete.

See Also

07 May 2026