umbraco-openapi-client by umbraco/umbraco-cms-backoffice-skills
npx skills add https://github.com/umbraco/umbraco-cms-backoffice-skills --skill umbraco-openapi-client切勿使用原始的 fetch() 调用与 Umbraco 后台 API 通信。 原始的 fetch 调用将导致 401 未授权错误,因为它们不包含 Umbraco 所需的承载令牌认证。
始终使用配置了 Umbraco 认证上下文的生成的 OpenAPI 客户端。 这确保了:
在以下情况下使用此模式:
[BackOfficeRoute] 创建自定义 C# API 控制器时设置包含 4 个部分:
@hey-api/openapi-ts 和 @hey-api/client-fetch广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
您的 API 必须通过 Swagger 公开。创建一个 composer:
// Composers/MyApiComposer.cs
using Asp.Versioning;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace MyExtension.Composers;
public class MyApiComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
// Register the API and Swagger
builder.Services.AddSingleton<ISchemaIdHandler, MySchemaIdHandler>();
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, MySwaggerGenOptions>();
builder.Services.Configure<UmbracoPipelineOptions>(options =>
{
options.AddFilter(new UmbracoPipelineFilter(Constants.ApiName)
{
SwaggerPath = $"/umbraco/swagger/{Constants.ApiName.ToLower()}/swagger.json",
SwaggerRoutePrefix = $"{Constants.ApiName.ToLower()}",
});
});
}
}
// Swagger schema ID handler
public class MySchemaIdHandler : SchemaIdHandler
{
public override bool CanHandle(Type type)
=> type.Namespace?.StartsWith("MyExtension") ?? false;
}
// Swagger generation options
public class MySwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
public void Configure(SwaggerGenOptions options)
{
options.SwaggerDoc(
Constants.ApiName,
new OpenApiInfo
{
Title = "My Extension API",
Version = "1.0",
});
}
}
// Constants
public static class Constants
{
public const string ApiName = "myextension";
}
添加到您的 Client/package.json:
{
"scripts": {
"generate-client": "node scripts/generate-openapi.js https://localhost:44325/umbraco/swagger/myextension/swagger.json"
},
"devDependencies": {
"@hey-api/client-fetch": "^0.10.0",
"@hey-api/openapi-ts": "^0.66.7",
"chalk": "^5.4.1",
"node-fetch": "^3.3.2"
}
}
创建 Client/scripts/generate-openapi.js:
import fetch from "node-fetch";
import chalk from "chalk";
import { createClient, defaultPlugins } from "@hey-api/openapi-ts";
console.log(chalk.green("Generating OpenAPI client..."));
const swaggerUrl = process.argv[2];
if (swaggerUrl === undefined) {
console.error(chalk.red(`ERROR: Missing URL to OpenAPI spec`));
process.exit(1);
}
// Ignore self-signed certificates on localhost
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
console.log(`Fetching OpenAPI definition from ${chalk.yellow(swaggerUrl)}`);
fetch(swaggerUrl)
.then(async (response) => {
if (!response.ok) {
console.error(chalk.red(`ERROR: ${response.status} ${response.statusText}`));
return;
}
await createClient({
input: swaggerUrl,
output: "src/api",
plugins: [
...defaultPlugins,
{
name: "@hey-api/client-fetch",
bundle: true,
exportFromIndex: true,
throwOnError: true,
},
{
name: "@hey-api/typescript",
enums: "typescript",
},
{
name: "@hey-api/sdk",
asClass: true,
},
],
});
console.log(chalk.green("Client generated successfully!"));
})
.catch((error) => {
console.error(`ERROR: ${chalk.red(error.message)}`);
});
在您的入口点中使用 Umbraco 的认证上下文配置生成的客户端:
// src/entrypoints/entrypoint.ts
import type { UmbEntryPointOnInit, UmbEntryPointOnUnload } 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 = (host, _extensionRegistry) => {
// CRITICAL: Configure the OpenAPI client with authentication
host.consumeContext(UMB_AUTH_CONTEXT, (authContext) => {
if (!authContext) return;
const config = authContext.getOpenApiConfiguration();
client.setConfig({
baseUrl: config.base,
credentials: config.credentials,
auth: config.token, // This provides the bearer token!
});
console.log("API client configured with auth");
});
};
export const onUnload: UmbEntryPointOnUnload = (_host, _extensionRegistry) => {
// Cleanup if needed
};
运行 npm run generate-client 后,使用生成的服务:
// In your workspace context, repository, or data source
import { MyExtensionService } from "../api/index.js";
// The client handles auth automatically!
const response = await MyExtensionService.getItems({
query: { skip: 0, take: 50 },
});
const item = await MyExtensionService.getItem({
path: { id: "some-guid" },
});
await MyExtensionService.createItem({
body: { name: "New Item", value: 123 },
});
npm run generate-clientsrc/api/ 中:
types.gen.ts - 来自您的 C# 模型的 TypeScript 类型sdk.gen.ts - 带有类型化方法的服务类client.gen.ts - HTTP 客户端配置index.ts - 重新导出所有内容// This will get 401 Unauthorized!
const response = await fetch('/umbraco/myextension/api/v1/items');
// Still fails - cookies don't work for Management API
const response = await fetch('/umbraco/myextension/api/v1/items', {
credentials: 'include'
});
// Client is configured with bearer token in entry point
const response = await MyExtensionService.getItems();
在以下位置查看完整的工作实现:
examples/notes-wiki/Client/ - 完整的 OpenAPI 客户端设置examples/tree-example/Client/ - 集成 OpenAPI 的树Composers/MyApiComposer.cs - Swagger 注册Client/scripts/generate-openapi.js - 生成脚本Client/src/entrypoints/entrypoint.ts - 认证配置Client/src/api/ - 生成的(请勿手动编辑)就是这样!始终生成您的 API 客户端并使用认证进行配置。切勿对经过认证的端点使用原始 fetch。
每周安装次数
72
仓库
GitHub 星标数
15
首次出现
2026年2月4日
安全审计
安装于
github-copilot53
cursor26
opencode24
codex24
gemini-cli22
amp22
NEVER use rawfetch() calls for Umbraco backoffice API communication. Raw fetch calls will result in 401 Unauthorized errors because they don't include the bearer token authentication that Umbraco requires.
ALWAYS use a generated OpenAPI client configured with Umbraco's auth context. This ensures:
Use this pattern whenever you:
[BackOfficeRoute]The setup has 4 parts:
@hey-api/openapi-ts and @hey-api/client-fetchYour API must be exposed via Swagger. Create a composer:
// Composers/MyApiComposer.cs
using Asp.Versioning;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
namespace MyExtension.Composers;
public class MyApiComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
// Register the API and Swagger
builder.Services.AddSingleton<ISchemaIdHandler, MySchemaIdHandler>();
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, MySwaggerGenOptions>();
builder.Services.Configure<UmbracoPipelineOptions>(options =>
{
options.AddFilter(new UmbracoPipelineFilter(Constants.ApiName)
{
SwaggerPath = $"/umbraco/swagger/{Constants.ApiName.ToLower()}/swagger.json",
SwaggerRoutePrefix = $"{Constants.ApiName.ToLower()}",
});
});
}
}
// Swagger schema ID handler
public class MySchemaIdHandler : SchemaIdHandler
{
public override bool CanHandle(Type type)
=> type.Namespace?.StartsWith("MyExtension") ?? false;
}
// Swagger generation options
public class MySwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
public void Configure(SwaggerGenOptions options)
{
options.SwaggerDoc(
Constants.ApiName,
new OpenApiInfo
{
Title = "My Extension API",
Version = "1.0",
});
}
}
// Constants
public static class Constants
{
public const string ApiName = "myextension";
}
Add to your Client/package.json:
{
"scripts": {
"generate-client": "node scripts/generate-openapi.js https://localhost:44325/umbraco/swagger/myextension/swagger.json"
},
"devDependencies": {
"@hey-api/client-fetch": "^0.10.0",
"@hey-api/openapi-ts": "^0.66.7",
"chalk": "^5.4.1",
"node-fetch": "^3.3.2"
}
}
Create Client/scripts/generate-openapi.js:
import fetch from "node-fetch";
import chalk from "chalk";
import { createClient, defaultPlugins } from "@hey-api/openapi-ts";
console.log(chalk.green("Generating OpenAPI client..."));
const swaggerUrl = process.argv[2];
if (swaggerUrl === undefined) {
console.error(chalk.red(`ERROR: Missing URL to OpenAPI spec`));
process.exit(1);
}
// Ignore self-signed certificates on localhost
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
console.log(`Fetching OpenAPI definition from ${chalk.yellow(swaggerUrl)}`);
fetch(swaggerUrl)
.then(async (response) => {
if (!response.ok) {
console.error(chalk.red(`ERROR: ${response.status} ${response.statusText}`));
return;
}
await createClient({
input: swaggerUrl,
output: "src/api",
plugins: [
...defaultPlugins,
{
name: "@hey-api/client-fetch",
bundle: true,
exportFromIndex: true,
throwOnError: true,
},
{
name: "@hey-api/typescript",
enums: "typescript",
},
{
name: "@hey-api/sdk",
asClass: true,
},
],
});
console.log(chalk.green("Client generated successfully!"));
})
.catch((error) => {
console.error(`ERROR: ${chalk.red(error.message)}`);
});
Configure the generated client with Umbraco's auth context in your entry point:
// src/entrypoints/entrypoint.ts
import type { UmbEntryPointOnInit, UmbEntryPointOnUnload } 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 = (host, _extensionRegistry) => {
// CRITICAL: Configure the OpenAPI client with authentication
host.consumeContext(UMB_AUTH_CONTEXT, (authContext) => {
if (!authContext) return;
const config = authContext.getOpenApiConfiguration();
client.setConfig({
baseUrl: config.base,
credentials: config.credentials,
auth: config.token, // This provides the bearer token!
});
console.log("API client configured with auth");
});
};
export const onUnload: UmbEntryPointOnUnload = (_host, _extensionRegistry) => {
// Cleanup if needed
};
After running npm run generate-client, use the generated service:
// In your workspace context, repository, or data source
import { MyExtensionService } from "../api/index.js";
// The client handles auth automatically!
const response = await MyExtensionService.getItems({
query: { skip: 0, take: 50 },
});
const item = await MyExtensionService.getItem({
path: { id: "some-guid" },
});
await MyExtensionService.createItem({
body: { name: "New Item", value: 123 },
});
npm run generate-clientsrc/api/:
types.gen.ts - TypeScript types from your C# modelssdk.gen.ts - Service class with typed methodsclient.gen.ts - HTTP client configurationindex.ts - Re-exports everything// This will get 401 Unauthorized!
const response = await fetch('/umbraco/myextension/api/v1/items');
// Still fails - cookies don't work for Management API
const response = await fetch('/umbraco/myextension/api/v1/items', {
credentials: 'include'
});
// Client is configured with bearer token in entry point
const response = await MyExtensionService.getItems();
See the complete working implementation in:
examples/notes-wiki/Client/ - Full OpenAPI client setupexamples/tree-example/Client/ - Tree with OpenAPI integrationComposers/MyApiComposer.cs - Swagger registrationClient/scripts/generate-openapi.js - Generation scriptClient/src/entrypoints/entrypoint.ts - Auth configurationClient/src/api/ - Generated (don't edit manually)That's it! Always generate your API client and configure it with auth. Never use raw fetch for authenticated endpoints.
Weekly Installs
72
Repository
GitHub Stars
15
First Seen
Feb 4, 2026
Security Audits
Gen Agent Trust HubWarnSocketPassSnykWarn
Installed on
github-copilot53
cursor26
opencode24
codex24
gemini-cli22
amp22
.NET和Python后端全局错误处理配置指南:异常中间件与自定义异常
220 周安装