> For the complete documentation index, see [llms.txt](https://docs.umbraco.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.umbraco.com/umbraco-cms/17.latest/extend-your-project/backoffice-extensions/foundation/fetching-data/custom-generated-client.md).

# Custom Generated Client

Learn how to create a custom-generated client with TypeScript types for your OpenAPI specification.

A generated client gives you a typed function for each endpoint of your own API. The [`@hey-api/openapi-ts`](https://heyapi.dev/openapi-ts/get-started) library generates the client from the OpenAPI document of your API. Umbraco generates the [Umbraco HTTP Client](/umbraco-cms/17.latest/extend-your-project/backoffice-extensions/foundation/fetching-data/http-client.md), `umbHttpClient`, with the same library.

Configure your client with `configureClient()`, as described in [Connecting to Umbraco](#connecting-to-umbraco). Your client then handles authentication and the login prompt like `umbHttpClient`. To show a notification from `tryExecute` when a request fails, also set `throwOnError`, as described in the same section.

Generate a client when you call many endpoints of your own API and want types for them. For the Management API, and for a few direct calls to your own API, use `umbHttpClient`.

## Generate your own client

To get started, install the generator using the following command:

```bash
npm install @hey-api/openapi-ts
```

Then, use the `openapi-ts` command to generate a client from your OpenAPI specification:

```bash
npx openapi-ts --input https://example.com/openapi.json --output src/api
```

The command generates a TypeScript client in the `src/api` folder. Import the generated client into your project to make requests to your API.

To learn more about OpenAPI and how to define your API specification, visit the [OpenAPI Documentation](https://swagger.io/specification/).

## Connecting to Umbraco

Your generated client needs the correct base URL, credentials, and authentication to talk to your API on the Umbraco server. Call `configureClient()` on the `UMB_AUTH_CONTEXT` to set up all three. The method:

* Sets `baseUrl` to the URL of the Umbraco server.
* Sets `credentials: 'include'`, so the browser sends the authentication cookies with every request.
* Sets the `auth` callback, which supplies the access token for requests that carry `security` metadata. See [How security metadata works](#how-security-metadata-works).
* Adds the default response interceptors of the backoffice. When a request gets a 401 response, they prompt the user to log in again and then retry `GET` requests. They also turn error responses into problem details, and show the notifications the server sends in the `Umb-Notifications` header.

{% hint style="info" %}
The [Umbraco Extension Template](/umbraco-cms/17.latest/extend-your-project/backoffice-extensions/development-flow/umbraco-extension-template.md) already includes this setup. If you scaffolded your extension with `dotnet new umbraco-extension`, authentication works out of the box.
{% endhint %}

To show a notification when a request fails, wrap the request in [`tryExecute`](/umbraco-cms/17.latest/extend-your-project/backoffice-extensions/foundation/fetching-data/try-execute.md). A generated client returns failed requests in an `error` property instead of throwing, and `tryExecute` only reports requests that throw. `configureClient()` does not change that. To make your client throw, set `throwOnError` on the `@hey-api/client-fetch` plugin in an `openapi-ts.config.ts` file:

{% code title="openapi-ts.config.ts" %}

```typescript
import { defineConfig } from '@hey-api/openapi-ts';

export default defineConfig({
    input: 'https://example.com/openapi.json',
    output: 'src/api',
    plugins: [
        {
            name: '@hey-api/client-fetch',
            throwOnError: true,
        },
        '@hey-api/typescript',
        '@hey-api/sdk',
    ],
});
```

{% endcode %}

You can also pass `throwOnError: true` to a single SDK function, for example `getMyControllerAction({ throwOnError: true })`.

### Using `configureClient()` (recommended)

Call `configureClient()` from the `onInit` function of a [Backoffice Entry Point](/umbraco-cms/17.latest/extend-your-project/backoffice-extensions/extending-overview/extension-types/backoffice-entry-point.md). An entry point runs once when the backoffice starts, which makes it the place to configure the client.

{% code title="src/entrypoints/entrypoint.ts" %}

```typescript
import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api';
import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
import { client } from '../api/client.gen.js';

export const onInit: UmbEntryPointOnInit = async (host) => {
  const authContext = await host.getContext(UMB_AUTH_CONTEXT);
  if (!authContext) {
    console.warn('UMB_AUTH_CONTEXT is not available, so the API client is not configured');
    return;
  }

  // Sets the base URL, credentials, and auth callback, and adds the default interceptors
  authContext.configureClient(client);
};
```

{% endcode %}

The `client` export comes from the `client.gen.ts` file that `@hey-api/openapi-ts` generates next to your SDK functions. `configureClient()` accepts any client generated by `@hey-api/openapi-ts`. Calling the method again for the same client has no effect.

### Using `runtimeConfigPath` (Umbraco 17.3 and earlier)

{% hint style="warning" %}
Use this setup only on Umbraco 17.3 and earlier, where `configureClient()` does not accept a client generated by your extension. It copies settings but not interceptors, so a 401 response fails instead of prompting the user to log in again. From Umbraco 17.7, it also fails to build with a client generated by an older `@hey-api/openapi-ts`. Switch to `configureClient()` when you upgrade. The [Umbraco Extension Template](/umbraco-cms/17.latest/extend-your-project/backoffice-extensions/development-flow/umbraco-extension-template.md) article lists the steps.
{% endhint %}

To pass plugin options like `runtimeConfigPath`, create a config file instead of using CLI flags.

1. Create `openapi-ts.config.ts` in your project root:

{% code title="openapi-ts.config.ts" %}

```typescript
import { defineConfig } from '@hey-api/openapi-ts';

export default defineConfig({
    input: 'https://localhost:44339/umbraco/swagger/myextension/swagger.json',
    output: 'src/api',
    plugins: [
        {
            name: '@hey-api/client-fetch',
            runtimeConfigPath: './src/hey-api.ts',
        },
        {
            name: '@hey-api/sdk',
            asClass: true,
        },
    ],
});
```

{% endcode %}

2. Run the generator, which will pick up the config file automatically:

```bash
npx openapi-ts
```

3. Create a `src/hey-api.ts` file and add the following:

{% code title="src/hey-api.ts" %}

```typescript
import type { CreateClientConfig } from './api/client.gen';
import { umbHttpClient } from '@umbraco-cms/backoffice/http-client';

export const createClientConfig: CreateClientConfig = (config) => ({
    ...config,
    ...umbHttpClient.getConfig(),
});
```

{% endcode %}

This copies `baseUrl`, `credentials`, `auth`, and `headers` from the backoffice HTTP client into your generated client at initialization time. Extensions load after the backoffice is fully configured, so all values are available.

When the generator runs, the generated `client.gen.ts` will automatically import `createClientConfig` and apply it during client creation. Your SDK functions will be authenticated without any entrypoint setup.

### Passing `umbHttpClient` directly

If you only have a few requests, you can skip client configuration entirely and pass `umbHttpClient` as the `client` parameter on each call. In an element that extends `UmbLitElement`:

{% code title="my-element.ts" %}

```typescript
import { getMyControllerAction } from './api/index.js';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
import { umbHttpClient } from '@umbraco-cms/backoffice/http-client';

const { data } = await tryExecute(this, getMyControllerAction({
    client: umbHttpClient,
}));

if (data) {
    console.log('Data:', data);
}
```

{% endcode %}

The example above uses the backoffice HTTP client directly instead of the generated client. The `umbHttpClient` already has authentication and the correct base URL configured.

## How security metadata works

The `auth` callback on `umbHttpClient` only fires when a request carries `security` metadata. Here is how that metadata flows from your API to the generated client:

1. Each operation in your OpenAPI spec has an optional `security` field listing the security schemes it requires. Example: `[{ "Backoffice-User": [] }]`.
2. Those scheme names are defined in `components.securitySchemes`, where their type (OAuth2, HTTP Bearer, and so on) is declared.
3. When hey-api generates your client, it reads both and resolves them into its own runtime shape - `security: [{ type: 'http', scheme: 'bearer' }]` - emitted directly into each generated SDK function.

The resolved shape is what `umbHttpClient` checks before invoking the `auth` callback.

### Management API (automatic)

If your controllers are part of the Umbraco Management API — tagged with `[MapToApi("management")]` — Umbraco writes the `operation.security` requirement into the OpenAPI spec automatically. This is done by `BackOfficeSecurityRequirementsOperationFilter`, a Swashbuckle operation filter registered as part of the Management API configuration. It inspects each operation at startup. If neither the controller class nor the action method carries `[AllowAnonymous]`, it adds a security requirement referencing the globally registered `"Backoffice-User"` scheme.

When hey-api generates your client from that spec, it resolves the scheme and emits the runtime security metadata into each SDK function. No extra setup needed.

### Custom separate API

If you expose a separate API with its own Swagger document, security requirements are not added automatically. You must apply them to each operation in your OpenAPI specification, for example via an operation filter. Umbraco provides `BackOfficeSecurityRequirementsOperationFilterBase` as a public base class you can subclass:

{% code title="MyExtensionSecurityRequirementsOperationFilter.cs" %}

```csharp
using Umbraco.Cms.Api.Management.OpenApi;

internal sealed class MyExtensionSecurityRequirementsOperationFilter : BackOfficeSecurityRequirementsOperationFilterBase
{
    // Must match the [MapToApi("...")] attribute on your controllers
    protected override string ApiName => "myextension";
}
```

{% endcode %}

Register it in your SwaggerGen configuration:

{% code title="MyExtensionApiComposer.cs" %}

```csharp
swaggerGenOptions.OperationFilter<MyExtensionSecurityRequirementsOperationFilter>();
```

{% endcode %}

No `AddSecurityDefinition` call is needed — Umbraco already registers the required Bearer security scheme globally, and the base filter references it automatically. Once in place, hey-api will generate SDK functions with the correct `security` metadata.

For a full walkthrough of setting up a custom API, see [Creating a Backoffice API](/umbraco-cms/17.latest/extend-your-project/tutorials/creating-a-backoffice-api.md).

### Direct calls without a generated client

When calling `umbHttpClient` directly — without a generated SDK function — there is no spec to derive security metadata from. Pass it explicitly on each call:

{% code title="my-element.ts" %}

```typescript
import { umbHttpClient } from '@umbraco-cms/backoffice/http-client';

const { data } = await umbHttpClient.get({
    url: '/umbraco/myextension/api/v1/endpoint',
    security: [{ type: 'http', scheme: 'bearer' }],
});
```

{% endcode %}

See [Umbraco HTTP Client](/umbraco-cms/17.latest/extend-your-project/backoffice-extensions/foundation/fetching-data/http-client.md) for more details.

## Further reading

* [@hey-api/openapi-ts](https://heyapi.dev/openapi-ts/get-started)
* [Creating a Backoffice API](/umbraco-cms/17.latest/extend-your-project/tutorials/creating-a-backoffice-api.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.umbraco.com/umbraco-cms/17.latest/extend-your-project/backoffice-extensions/foundation/fetching-data/custom-generated-client.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
