# 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](apps-quick-start-guide.html).

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.

> **Note: Experimental Feature**
> Enhanced DX is currently experimental. The commands, generated files, and app package structure may change in future versions.

## 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](https://nodejs.org/en/) version 18.20.4 or later installed on your machine

* Access to a YouTrack instance where you can upload apps

* A [permanent token](https://www.jetbrains.com.cn/en-us/help/youtrack/server/manage-permanent-token.html) 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.

Procedure: To create an app package with Enhanced DX:

1. Prepare an empty directory for your app.

2. Run the app generator:

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

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

```SHELL
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](apps-reference-http-handlers.html).

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](apps-global-project-level.html#scopes-http-handlers) 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.

```TYPESCRIPT
/**
 * @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:

```TYPESCRIPT
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](app-permissions.html) are required to access a handler, use the `withPermissions` helper function from the Enhanced DX runtime package.

```TYPESCRIPT
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](apps-reference-extension-points.html) and update the app configuration automatically.
:
:
:
:
: ```SHELL
: 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.
:
:
:
:
: ```SHELL
: 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.
:
:
:
:
: ```SHELL
: 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.
:
:
:
:
: ```SHELL
: 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.

```TYPESCRIPT
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:

```TYPESCRIPT
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

* [App Quick Start Guide](apps-quick-start-guide.html)

* [HTTP Handlers](apps-reference-http-handlers.html)

* [App Settings](app-settings.html)

* [Extension Properties](apps-extension-properties.html)

