惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
阮一峰的网络日志
阮一峰的网络日志
Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
罗磊的独立博客
S
SegmentFault 最新的问题
V
V2EX
V
Visual Studio Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
博客园 - 三生石上(FineUI控件)
Stack Overflow Blog
Stack Overflow Blog
Y
Y Combinator Blog
MyScale Blog
MyScale Blog
D
Docker
Google DeepMind News
Google DeepMind News
Blog — PlanetScale
Blog — PlanetScale
M
Microsoft Research Blog - Microsoft Research
Martin Fowler
Martin Fowler
S
Secure Thoughts
B
Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Recent Announcements
Recent Announcements
MongoDB | Blog
MongoDB | Blog
C
Cisco Blogs
C
CERT Recently Published Vulnerability Notes
T
True Tiger Recordings
GbyAI
GbyAI
P
Proofpoint News Feed
P
Privacy International News Feed
Jina AI
Jina AI
The Cloudflare Blog
I
Intezer
AWS News Blog
AWS News Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
Security Archives - TechRepublic
NISL@THU
NISL@THU
The Register - Security
The Register - Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
P
Palo Alto Networks Blog
S
Schneier on Security
L
LINUX DO - 热门话题
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
C
Cybersecurity and Infrastructure Security Agency CISA

DEV Community

An open source LLM eval tool with two independent quality signals Using Dashboard Filtering to Get Customer Usage in Seconds from TBs of Data Skills, Java 17, And Theme Accents 4 Hard Lessons on Optimizing AI Coding Agents Arctype: Cross-Platform Database GUI for LLM Artifacts Your robots.txt says GPTBot is welcome. Your server says 403. Organizing How to Use AWS Glue Workflow 5 n8n Automations Every Digital Agency Should Be Running (Bill More, Work Less) Getting Started with TorchGeo — Remote Sensing with PyTorch Designing a Scalable Cross-Platform Appium Framework Google Antigravity 2.0 & Slash Commands Building a Unified Adaptive Learning Intelligence with Gemma 4, Flutter, and Multi-Model Orchestration Looking for beta testers for a £60 server management application The Disk-Pressure Incident That Taught Me to Always Set LimitRanges and Other Lessons from Mirroring EKS Locally. Why AI Should Not Write SQL Against ERP Databases Vibe coding works until it doesn't. The debt is real. Shipping at the Edge: Migrating a Coffee Subscription Platform to Cloudflare Workers Stop Tab-Switching: A Developer's Guide to Color Tools That Actually Fit the Workflow DevOps vs MLOps vs AIOps: What Changes, What Stays, and a Simple Roadmap to Get Started Run Powerful AI Coding Locally on a Normal Laptop 5 n8n Automations Every WooCommerce Store Needs (Save 10+ Hours/Week) What I Learned Building My Own AI Harness Hytale Servers Will Fail Treasure Hunts Until We Fix Our Event Handling Redux in React: Managing Global State Like a Pro Unfreezing Your GitHub Actions: Troubleshooting Stuck Deployments and Protecting Your Git Repo Statistics Unlocking Project Discoverability on GHES: A Key to Software Engineering Productivity When the Cleanup Code Becomes the Project Rockpack 8.0 - A React Scaffolder Built for the Age of AI-Assisted Development Mismanaging the Treasure Hunt Engine in Hytale Servers Will Get You Killed Why Hardcoded Automations Fail AI Agents Stop Calling It an AI Assistant. It’s Already Managing Your Company Why I built a post-quantum signing API (and why JWT is on borrowed time) Weekend Thought: Frontend Build Tools Suffer From Work Amnesia A 10-Line Playwright Trick That Saved Me Hours on Every Sephora Run AI Is Changing Engineering Culture More Than We Realize Everyone Was Focused on Gemini, But Infinite Scaler Was the Real Twister "Gemma 4 Analyzed My Bank Statements – Apparently I 'Have a Problem' with Coffee and Late-Night Apps" #css #webdev #beginners #codenewbie The Hidden Layer Every AI Developer Must Learn AlphaEvolve: Google DeepMind's Gemini-Powered Evolutionary Coding Agent RDS Reserved Instance Pricing: Every Engine, Every Rule, Real Dollar Savings How To Build An AI-Powered MVP Without Burning Your Startup Budget In 2026 Reading a Psychrometric Chart Without Getting Lost LMR-BENCH: Can LLM Agents Reproduce NLP Research Code? (EMNLP 2025) How to turn text into colors (without AI) Building Real-Time Apps in Node.js with Rivalis: WebSockets, Rooms, Actors, and a Binary Wire This Week In React #282 : Security, Fate, TanStack, Redux, Jotai | Hermes-node, Expo, Rozenite, Harness | TC39, Bun, pnpm, npm, Yarn, Node AI Copilot vs AI Agent Architecture - What's Actually Different (And Why It Matters) Smart Contract Security: NEAR's Futures Surge and AI Token Risks Database Maintenance: Tracing Production Incidents to Their Root Cause Stop juggling AI SDKs in PHP — meet Prisma Google Quietly Changed What “Apps” Mean at I/O 2026 The Infrastructure Team Is the Real Single Point of Failure Building SQLite from Scratch: 740 Lines of C++23 to Understand Every Byte of a .db File The 4 Levels of Hermes Agent Scaling Framework: From One Hermes Agent to a Fully Automated Team Your AI Has a Memory. It Just Doesn’t Know What to Remember. Claprec: Engineering Tradeoffs - Limited time vs. Perfection (6/6) Building a Daily Google News API Monitor in Python Building RookDuel Avikal: From Chess Steganography to Post-Quantum Archival Security Google I/O e IA: o que realmente muda na vida do dev? Color Contrast Failures: The Number One Accessibility Issue and How to Fix It # I Watched 15 Hours of Hermes Agent Videos So You Don't Have To Cómo solucionar el bucle infinito en useEffect con objetos y arrays en React The First Agent-Centric Cloud Security Platform — And Why We Didn't Build It That Way On Purpose Most Treasure Hunts Engines on Hytale Servers Are Built to Fail - Lessons from a Burned Database GhostScan v3.0 — From Closed-Source EXE to Open-Source Pentest Framework De hojas de cálculo a IA: construyendo una plataforma SRM moderna When is AI fine in education? Python Tools for Managing API Rate Limits in Data Pipelines How to Implement Exponential Backoff for Rate-Limited APIs in Python "My Web Chat Wasn't a Real Channel. That Broke My Agent Pipeline" next-advanced-sitemap v1.0.7 — safer URL ingestion & automatic trimming for Next.js sitemap generation I keep seeing people build an AI lead processing agent when they really need a 6-step rules engine AI Powered Student Learning Assistant Using Gemma 4 How I Built a Drop-In Proxy to Slash My OpenAI Bills by 20%+ Automatically Building a Sarcastic AI English Tutor with Persona-as-Code and Gemini Audio Input for Pronunciation Correction Five Years Later, I Finally Have 96GB VRAM — What It Actually Unlocks for Agent Loops Turning a 1-Line Idea Into a 40-Second Short with a 10-Beat Local Video Pipeline Running LTX-2.3 Alongside TTS on a Single 96GB GPU with a Cold-Start Architecture Cutting LTX-2 22B Peak VRAM by 40% with fp8_cast — and Why optimum-quanto Was a Trap HiDream Skeleton Mode: Prompt Beats OpenPose Ref — 8 Patterns Benchmarked Replicating a Language-Learning Comedy Short with Claude Code — Gemini as a Multimodal Sub-Agent HiDream-O1-Image 3–8x Faster: Benchmarking Steps, CFG, and Resolution AWS Savings Plan Buying Strategy: How to Layer, Size, and Time Commitments application.properties I built a macro tracker powered by AI + attitude Solace: A Global Mental Health First Responder Built with Gemma 4 Why Blocking Prompt Injection Is Wrong — and What to Do Instead The AI code tools Dutch developers actually use in 2026 (field notes) Automatic Error Recovery in AI Agent Networks You Are Not Choosing Building a Cinematic Adaptive Learning Intelligence with Gemma 4, Gemini, and OpenAI(Powered by Gemma 4) CLAUDE.md for Angular: 13 Rules That Make AI Write Idiomatic, Production-Ready Components I tested 7 vector databases for my RAG stack in 2026, here's the one nobody is talking about (yet) Claude agreed with a false fact I gave it. Confidently. That broke my workflow Google's "Budget" Model Just Beat Its Own Flagship. Here's What That Actually Means for Developers. How I built a monitoring SaaS for Joomla, WordPress & PrestaShop agencies Shifting from Passive Dashboards to Automated Remediation: A Guide to Next-Generation FinOps and CloudZero Alternatives Automating CSV WooCommerce Imports Without Plugins Why Wobbly Plugs and Overheating Outlets Are More Dangerous Than You Think (UL 498 Explained)
Umbraco 18 and OpenAPI: a heads-up for extension developers
Laura Neto · 2026-05-19 · via DEV Community

Time to upgrade your extension to Umbraco 18?
A friendly heads up, the OpenAPI generation got an overhaul.

Microsoft.AspNetCore.OpenApi is Umbraco's new library of choice for OpenAPI document generation, replacing Swashbuckle.AspNetCore. It ships with ASP.NET Core and is maintained by Microsoft alongside the framework, which means one fewer third-party dependency to worry about.

The trade-off: if your extension wired up its own OpenAPI document on v17, it won't compile against v18. The Swashbuckle types it relied on are gone.

Two paths forward:

  1. Migrate to Microsoft OpenAPI. Two options:
  2. Keep using Swashbuckle (not recommended). Install Swashbuckle.AspNetCore in your own extension, wire it up, then plug your document into Umbraco's Swagger UI via AddOpenApiDocumentToUi(...). When rewriting isn't an option right now.

ℹ️ Note: the OpenAPI spec URL also changed from /umbraco/swagger/{documentName}/swagger.json to /umbraco/openapi/{documentName}.json. If you generate a client from the spec (e.g. via the v17 extension template's package.json generate-client script), update the URL before regenerating.

Use the AddBackOfficeOpenApiDocument extension method

We added AddBackOfficeOpenApiDocument() in v18 to take the boilerplate out of registering an OpenAPI document. It's an extension method on IUmbracoBuilder that wires up Umbraco's sensible defaults so you don't have to.

Out of the box, the helper registers your document, scopes it to endpoints decorated with [MapToApi(documentName)], applies Umbraco's naming conventions for schemas and operation IDs, and adds the document to the Swagger UI dropdown at /umbraco/openapi/.

⚠️ Watch out: the first time you call AddBackOfficeOpenApiDocument (or AddOpenApi directly) and build, you'll hit error CS9137: The 'interceptors' feature is not enabled in this namespace. The Microsoft.AspNetCore.OpenApi source generator (which surfaces your XML doc comments as descriptions in the OpenAPI document, among other compile-time enrichments) propagates through Umbraco's project reference, but the property that enables it ships only with the Web SDK. Class-library projects don't get it automatically, so add it to your .csproj:

<PropertyGroup>
  <InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>
</PropertyGroup>

Minimal usage

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddBackOfficeOpenApiDocument("my-api");
    }
}

Enter fullscreen mode Exit fullscreen mode

This will add and configure a "my-api" OpenAPI document that will display the controllers/endpoints that have the attribute [MapToApi("my-api")].

Typical usage

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddBackOfficeOpenApiDocument("my-api", document => document
            .WithTitle("My API")
            .WithBackOfficeAuthentication());
    }
}

Enter fullscreen mode Exit fullscreen mode

Besides adding a "my-api" OpenAPI document, this will set the document title to "My API" and declare that the document's operations require backoffice authentication (which is what enables the "Authorize" flow in the Swagger UI).

Builder method reference

Method What it does
WithTitle(string) Sets the document's Info.Title. Also used as the UI dropdown label unless WithUiTitle is set.
WithUiTitle(string) Overrides only the UI dropdown label, leaving Info.Title untouched.
ExcludeFromUi() Suppresses registration in the Swagger UI document dropdown. Document is still reachable at its JSON URL.
WithBackOfficeAuthentication() Registers the backoffice OAuth2 security scheme on the document and marks operations as requiring authentication. Swagger UI uses this to prompt for login; generated clients use it to send the token.
ConfigureOpenApiOptions(Action<OpenApiOptions>) Adds a configuration callback for the underlying OpenApiOptions. Multiple calls will run in order after Umbraco's defaults.
WithJsonOptions(...) Sets the JsonOptions used when generating the document's schema. See Aligning schema serialization below.

Aligning schema serialization

Schema generation needs to use the same JsonOptions your API uses at runtime, otherwise the generated SDKs and the actual payloads will not match. By default, Microsoft.AspNetCore.OpenApi generates schemas using the global HTTP JsonOptions, which means whoever hosts your extension can change them and that affects your schema.

For most backoffice extensions you want the BackOffice named JsonOptions (the same ones Umbraco's Management API uses). Point the document at them with WithJsonOptions:

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddBackOfficeOpenApiDocument("my-api", document => document
            .WithJsonOptions(Constants.JsonOptionsNames.BackOffice));
    }
}

Enter fullscreen mode Exit fullscreen mode

For this to be meaningful at runtime, your controllers also need to serialize with those same options. The v17 extension template inherits from a plain ControllerBase, which uses the default options instead. Fix that by adding [JsonOptionsName(Constants.JsonOptionsNames.BackOffice)] to your own base controller or to each controller directly.

Without it, the schema reflects backoffice serialization but the actual responses are serialized with the default options, and you're back to the schema-vs-runtime mismatch.

Going beyond the defaults

The helper is not a closed box. Anything you'd normally do on OpenApiOptions (custom operation/schema/document transformers, your own CreateSchemaReferenceId, a replacement operation ID transformer, etc.) is still available through ConfigureOpenApiOptions. It just runs after Umbraco's defaults, so you compose on top of them rather than starting from scratch.

For example, registering your own schema transformer:

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddBackOfficeOpenApiDocument("my-api", document => document
            .WithTitle("My API")
            .ConfigureOpenApiOptions(options =>
                options.AddSchemaTransformer<MyExtensionSchemaTransformer>()));
    }
}

Enter fullscreen mode Exit fullscreen mode

Migrating an existing composer

If your extension followed the shape of the v17 dotnet template, the migration is: delete the old composer body and write the AddBackOfficeOpenApiDocument(...) call. Below is the same extension's composer before and after.

v17 (Swashbuckle)

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.AddSingleton<IOperationIdHandler, CustomOperationHandler>();

        builder.Services.Configure<SwaggerGenOptions>(opt =>
        {
            opt.SwaggerDoc(Constants.ApiName, new OpenApiInfo
            {
                Title = "My Test Extension Backoffice API",
            });

            opt.OperationFilter<UmbracoExtensionOperationSecurityFilter>();
        });
    }

    public class UmbracoExtensionOperationSecurityFilter : BackOfficeSecurityRequirementsOperationFilterBase
    {
        protected override string ApiName => Constants.ApiName;
    }

    public class CustomOperationHandler : OperationIdHandler
    {
        // ...
    }
}

Enter fullscreen mode Exit fullscreen mode

v18 (Microsoft OpenAPI + new builder)

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder) =>
        builder.AddBackOfficeOpenApiDocument(
            Constants.ApiName,
            document => document
                .WithTitle("My Test Extension Backoffice API")
                .WithBackOfficeAuthentication());
}

Enter fullscreen mode Exit fullscreen mode

The helper registers UmbracoOperationIdTransformer by default, so the old IOperationIdHandler is no longer needed. If you want custom operation ID logic, register your own through ConfigureOpenApiOptions:

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddBackOfficeOpenApiDocument("my-api", document => document
            .ConfigureOpenApiOptions(options =>
                options.AddOperationTransformer((operation, context, _) =>
                {
                    operation.OperationId = $"{context.Description.ActionDescriptor.RouteValues["action"]}";
                    return Task.CompletedTask;
                })));
    }
}

Enter fullscreen mode Exit fullscreen mode

Use Microsoft's AddOpenApi method directly

If you want to register a document that isn't a backoffice document, or you want to start from a clean slate without Umbraco's defaults, call AddOpenApi yourself. The helper is just a thin wrapper around that method. Umbraco already wires the OpenAPI routing and Swagger UI hosting, so you don't need to call MapOpenApi or configure endpoints. You only need to register your documents.

For the transformer model itself (how operation/schema/document transformers work), see Microsoft's Customize OpenAPI documents docs.

For Umbraco's specifics ([MapToApi] filtering via options.ShouldInclude, schema/operation ID conventions, the public UmbracoSchemaIdGenerator.Generate(type) helper), see the API versioning and OpenAPI docs.

A few things worth remembering:

  • Backoffice authentication: call options.AddBackofficeSecurityRequirements() (from Umbraco.Cms.Api.Management.OpenApi) instead of subclassing BackOfficeSecurityRequirementsOperationFilterBase.
  • Swagger UI registration: documents are no longer auto-listed. Call services.AddOpenApiDocumentToUi(documentName, uiTitle) to surface yours in the dropdown.
  • InterceptorsNamespaces opt-in: same as the helper path. See the Watch out callout at the top of the helper section.

Keep using Swashbuckle

Not recommended. Consider this only if rewriting a complex Swashbuckle setup isn't feasible right now. Trade-offs:

  • Extra dependency on Swashbuckle.AspNetCore.
  • OpenAPI 3.0 output, whereas the rest of Umbraco's documents emit 3.1.
  • Extra wiring you maintain yourself.
  • Both libraries depend on Microsoft.OpenApi. A breaking change there that Swashbuckle doesn't follow leaves your extension incompatible with the rest of the stack.

If you still want to go this route, see the Swashbuckle.AspNetCore README for general setup. The rest of this section is the Umbraco-specific wiring.

1. Install the NuGet package

Add a PackageReference to Swashbuckle.AspNetCore in your extension's .csproj (or dotnet add package Swashbuckle.AspNetCore).

2. Register Swashbuckle and your document in a composer

In v17, Umbraco called AddSwaggerGen for you and your extension only configured SwaggerGenOptions. In v18 you have to call it yourself:

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.AddSwaggerGen(opt =>
        {
            opt.SwaggerDoc(Constants.ApiName, new OpenApiInfo
            {
                Title = "My Test Extension Backoffice API",
                Version = "1.0",
            });

            var previousDocInclusion = opt.SwaggerGeneratorOptions.DocInclusionPredicate;
            opt.DocInclusionPredicate((docName, apiDesc) =>
                docName == Constants.ApiName
                    ? apiDesc.ActionDescriptor.HasMapToApiAttribute(docName)
                    : previousDocInclusion?.Invoke(docName, apiDesc) ?? true);

            // any operation filters, schema filters, or other v17 setup you had...
        });
    }
}

Enter fullscreen mode Exit fullscreen mode

3. Host the Swagger JSON middleware via an IUmbracoPipelineFilter

Umbraco no longer calls app.UseSwagger(), so the middleware that serves the Swagger JSON isn't running. Add it through Umbraco's pipeline filter, and set the RouteTemplate to match UmbracoOpenApiOptions.RouteTemplate (default umbraco/openapi/{documentName}.json) so the dropdown link resolves.

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.Configure<UmbracoPipelineOptions>(options =>
        {
            options.AddFilter(new UmbracoPipelineFilter("Swashbuckle")
            {
                PostPipeline = app =>
                {
                    var openApiOptions = app.ApplicationServices.GetRequiredService<IOptions<UmbracoOpenApiOptions>>().Value;
                    if (!openApiOptions.Enabled)
                    {
                        return;
                    }

                    var routeTemplate = openApiOptions.RouteTemplate;
                    app.UseWhen(
                        ctx => ctx.Request.Path == routeTemplate.Replace("{documentName}", Constants.ApiName).EnsureStartsWith('/'),
                        branch => branch.UseSwagger(c => c.RouteTemplate = routeTemplate));
                },
            });
        });
    }
}

Enter fullscreen mode Exit fullscreen mode

Scope the middleware to your own document's URL. Otherwise Swashbuckle intercepts requests for Umbraco's own documents (Management, Delivery) and returns 404 because they aren't registered with AddSwaggerGen.

4. Register the document in the Swagger UI dropdown

Register the document in Umbraco's Swagger UI dropdown:

public class UmbracoExtensionApiComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.AddOpenApiDocumentToUi(Constants.ApiName, "My API");
    }
}

Enter fullscreen mode Exit fullscreen mode

TL;DR

The helper is the simplest option for most extensions. AddBackOfficeOpenApiDocument() is what the v18 template uses, gives you Umbraco's sensible defaults out of the box (naming conventions, Swagger UI registration, JsonOptions alignment), and lets you register an OpenAPI document in a single call.

Calling AddOpenApi yourself is the standard ASP.NET Core approach and works fine. It means more wiring and code to maintain, so reach for it when you need full control (non-backoffice documents, or skipping the Umbraco defaults).

If rewriting really isn't an option right now, keeping Swashbuckle as your own dependency and plugging into the dropdown via AddOpenApiDocumentToUi(...) will get you through, but plan to revisit when you can.

Further reading