<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Laura Neto</title>
    <description>The latest articles on DEV Community by Laura Neto (@lauraneto).</description>
    <link>https://dev.to/lauraneto</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3938746%2F93a4492c-191f-4862-8c3a-13d3961a1c2c.jpg</url>
      <title>DEV Community: Laura Neto</title>
      <link>https://dev.to/lauraneto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lauraneto"/>
    <language>en</language>
    <item>
      <title>Umbraco 18 and OpenAPI: a heads-up for extension developers</title>
      <dc:creator>Laura Neto</dc:creator>
      <pubDate>Tue, 19 May 2026 13:37:37 +0000</pubDate>
      <link>https://dev.to/lauraneto/umbraco-18-and-openapi-a-heads-up-for-extension-developers-1k7</link>
      <guid>https://dev.to/lauraneto/umbraco-18-and-openapi-a-heads-up-for-extension-developers-1k7</guid>
      <description>&lt;p&gt;Time to upgrade your extension to Umbraco 18?&lt;br&gt;
A friendly heads up, the OpenAPI generation got an overhaul.&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Two paths forward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Migrate to Microsoft OpenAPI&lt;/strong&gt;. Two options:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use the &lt;code&gt;AddBackOfficeOpenApiDocument&lt;/code&gt; extension method&lt;/strong&gt;: Umbraco's new helper. What the v18 template uses. Covers most extensions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Microsoft's &lt;code&gt;AddOpenApi&lt;/code&gt; method directly&lt;/strong&gt;: call &lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt; yourself. For full control.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep using Swashbuckle&lt;/strong&gt; &lt;em&gt;(not recommended)&lt;/em&gt;. Install &lt;code&gt;Swashbuckle.AspNetCore&lt;/code&gt; in your own extension, wire it up, then plug your document into Umbraco's Swagger UI via &lt;code&gt;AddOpenApiDocumentToUi(...)&lt;/code&gt;. When rewriting isn't an option right now.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ &lt;strong&gt;Note:&lt;/strong&gt; the OpenAPI spec URL also changed from &lt;code&gt;/umbraco/swagger/{documentName}/swagger.json&lt;/code&gt; to &lt;code&gt;/umbraco/openapi/{documentName}.json&lt;/code&gt;. If you generate a client from the spec (e.g. via the v17 extension template's &lt;code&gt;package.json&lt;/code&gt; &lt;code&gt;generate-client&lt;/code&gt; script), update the URL before regenerating.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Use the AddBackOfficeOpenApiDocument extension method
&lt;/h2&gt;

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

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

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Watch out:&lt;/strong&gt; the first time you call &lt;code&gt;AddBackOfficeOpenApiDocument&lt;/code&gt; (or &lt;code&gt;AddOpenApi&lt;/code&gt; directly) and build, you'll hit &lt;code&gt;error CS9137: The 'interceptors' feature is not enabled in this namespace&lt;/code&gt;. The &lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt; 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 &lt;code&gt;.csproj&lt;/code&gt;:&lt;/p&gt;


&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;InterceptorsNamespaces&amp;gt;&lt;/span&gt;$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated&lt;span class="nt"&gt;&amp;lt;/InterceptorsNamespaces&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Minimal usage
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UmbracoExtensionApiComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddBackOfficeOpenApiDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-api"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will add and configure a "my-api" OpenAPI document that will display the controllers/endpoints that have the attribute &lt;code&gt;[MapToApi("my-api")]&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Typical usage
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UmbracoExtensionApiComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddBackOfficeOpenApiDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My API"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithBackOfficeAuthentication&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;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).&lt;/p&gt;
&lt;h3&gt;
  
  
  Builder method reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WithTitle(string)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sets the document's &lt;code&gt;Info.Title&lt;/code&gt;. Also used as the UI dropdown label unless &lt;code&gt;WithUiTitle&lt;/code&gt; is set.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WithUiTitle(string)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Overrides only the UI dropdown label, leaving &lt;code&gt;Info.Title&lt;/code&gt; untouched.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ExcludeFromUi()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Suppresses registration in the Swagger UI document dropdown. Document is still reachable at its JSON URL.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WithBackOfficeAuthentication()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;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.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ConfigureOpenApiOptions(Action&amp;lt;OpenApiOptions&amp;gt;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Adds a configuration callback for the underlying &lt;code&gt;OpenApiOptions&lt;/code&gt;. Multiple calls will run in order after Umbraco's defaults.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WithJsonOptions(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sets the &lt;code&gt;JsonOptions&lt;/code&gt; used when generating the document's schema. See Aligning schema serialization below.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Aligning schema serialization
&lt;/h3&gt;

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

&lt;p&gt;For most backoffice extensions you want the BackOffice named &lt;code&gt;JsonOptions&lt;/code&gt; (the same ones Umbraco's Management API uses). Point the document at them with &lt;code&gt;WithJsonOptions&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UmbracoExtensionApiComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddBackOfficeOpenApiDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithJsonOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JsonOptionsNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BackOffice&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;ControllerBase&lt;/code&gt;, which uses the default options instead. Fix that by adding &lt;code&gt;[JsonOptionsName(Constants.JsonOptionsNames.BackOffice)]&lt;/code&gt; to your own base controller or to each controller directly.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Going beyond the defaults
&lt;/h3&gt;

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

&lt;p&gt;For example, registering your own schema transformer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UmbracoExtensionApiComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddBackOfficeOpenApiDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My API"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureOpenApiOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSchemaTransformer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyExtensionSchemaTransformer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Migrating an existing composer
&lt;/h3&gt;

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

&lt;p&gt;&lt;strong&gt;v17 (Swashbuckle)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UmbracoExtensionApiComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOperationIdHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CustomOperationHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SwaggerGenOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SwaggerDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiInfo&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"My Test Extension Backoffice API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperationFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UmbracoExtensionOperationSecurityFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UmbracoExtensionOperationSecurityFilter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackOfficeSecurityRequirementsOperationFilterBase&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ApiName&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomOperationHandler&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OperationIdHandler&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;v18 (Microsoft OpenAPI + new builder)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UmbracoExtensionApiComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddBackOfficeOpenApiDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My Test Extension Backoffice API"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithBackOfficeAuthentication&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The helper registers &lt;code&gt;UmbracoOperationIdTransformer&lt;/code&gt; by default, so the old &lt;code&gt;IOperationIdHandler&lt;/code&gt; is no longer needed. If you want custom operation ID logic, register your own through &lt;code&gt;ConfigureOpenApiOptions&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UmbracoExtensionApiComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddBackOfficeOpenApiDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureOpenApiOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOperationTransformer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperationId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActionDescriptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RouteValues&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;})));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use Microsoft's AddOpenApi method directly
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;AddOpenApi&lt;/code&gt; 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 &lt;code&gt;MapOpenApi&lt;/code&gt; or configure endpoints. You only need to register your documents.&lt;/p&gt;

&lt;p&gt;For the transformer model itself (how operation/schema/document transformers work), see Microsoft's &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/customize-openapi" rel="noopener noreferrer"&gt;Customize OpenAPI documents&lt;/a&gt; docs.&lt;/p&gt;

&lt;p&gt;For Umbraco's specifics (&lt;code&gt;[MapToApi]&lt;/code&gt; filtering via &lt;code&gt;options.ShouldInclude&lt;/code&gt;, schema/operation ID conventions, the public &lt;code&gt;UmbracoSchemaIdGenerator.Generate(type)&lt;/code&gt; helper), see the &lt;a href="https://docs.umbraco.com/umbraco-cms/18.latest/extend-your-project/server-side-extensions/api-versioning-and-openapi" rel="noopener noreferrer"&gt;API versioning and OpenAPI&lt;/a&gt; docs.&lt;/p&gt;

&lt;p&gt;A few things worth remembering:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Keep using Swashbuckle
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Not recommended.&lt;/strong&gt; Consider this only if rewriting a complex Swashbuckle setup isn't feasible right now. Trade-offs:&lt;/p&gt;

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

&lt;p&gt;If you still want to go this route, see the &lt;a href="https://github.com/domaindrivendev/Swashbuckle.AspNetCore#getting-started" rel="noopener noreferrer"&gt;Swashbuckle.AspNetCore README&lt;/a&gt; for general setup. The rest of this section is the Umbraco-specific wiring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install the NuGet package&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a &lt;code&gt;PackageReference&lt;/code&gt; to &lt;code&gt;Swashbuckle.AspNetCore&lt;/code&gt; in your extension's &lt;code&gt;.csproj&lt;/code&gt; (or &lt;code&gt;dotnet add package Swashbuckle.AspNetCore&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Register Swashbuckle and your document in a composer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In v17, Umbraco called &lt;code&gt;AddSwaggerGen&lt;/code&gt; for you and your extension only configured &lt;code&gt;SwaggerGenOptions&lt;/code&gt;. In v18 you have to call it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UmbracoExtensionApiComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSwaggerGen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SwaggerDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiInfo&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"My Test Extension Backoffice API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;previousDocInclusion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SwaggerGeneratorOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DocInclusionPredicate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DocInclusionPredicate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;docName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;docName&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiName&lt;/span&gt;
                    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;apiDesc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActionDescriptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasMapToApiAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;previousDocInclusion&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// any operation filters, schema filters, or other v17 setup you had...&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Host the Swagger JSON middleware via an &lt;code&gt;IUmbracoPipelineFilter&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UmbracoExtensionApiComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UmbracoPipelineOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UmbracoPipelineFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Swashbuckle"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;PostPipeline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;openApiOptions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplicationServices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UmbracoOpenApiOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;().&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;openApiOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;

                    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;routeTemplate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openApiOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RouteTemplate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseWhen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;routeTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{documentName}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;EnsureStartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSwagger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RouteTemplate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;routeTemplate&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;AddSwaggerGen&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Register the document in the Swagger UI dropdown&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Register the document in Umbraco's Swagger UI dropdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UmbracoExtensionApiComposer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComposer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUmbracoBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenApiDocumentToUi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"My API"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;The helper is the simplest option for most extensions. &lt;code&gt;AddBackOfficeOpenApiDocument()&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Calling &lt;code&gt;AddOpenApi&lt;/code&gt; 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).&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/umbraco/Announcements/issues/32" rel="noopener noreferrer"&gt;Announcements #32&lt;/a&gt;: the official announcement, with the full set of changes and reasoning&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.umbraco.com/umbraco-cms/18.latest/extend-your-project/server-side-extensions/custom-backoffice-api" rel="noopener noreferrer"&gt;Custom Backoffice API&lt;/a&gt;: end-to-end backoffice API setup&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.umbraco.com/umbraco-cms/18.latest/extend-your-project/server-side-extensions/api-versioning-and-openapi" rel="noopener noreferrer"&gt;API versioning and OpenAPI&lt;/a&gt;: schema IDs, operation IDs, route configuration, endpoint filtering&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.umbraco.com/umbraco-cms/18.latest/get-started/upgrading-and-migrating/version-specific#umbraco-18" rel="noopener noreferrer"&gt;Umbraco 18 version-specific upgrade docs&lt;/a&gt;: official migration reference&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>umbraco</category>
      <category>openapi</category>
    </item>
  </channel>
</rss>
