Skip to content

Open API: Add fluent builder for registering custom backoffice OpenAPI documents#22774

Merged
lauraneto merged 12 commits into
release/18.0from
v18/feature/add-back-office-open-api-document-extension
May 12, 2026
Merged

Open API: Add fluent builder for registering custom backoffice OpenAPI documents#22774
lauraneto merged 12 commits into
release/18.0from
v18/feature/add-back-office-open-api-document-extension

Conversation

@lauraneto

@lauraneto lauraneto commented May 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds IUmbracoBuilder.AddBackOfficeOpenApiDocument(...) for registering a custom OpenAPI document with Umbraco's defaults.
  • Migrates the built-in Management API OpenAPI registration to use the new helper. ConfigureUmbracoManagementApiOpenApiOptions is removed; all per-document opinions (Info metadata, security requirements, transformers, JSON options) move into the configuration callback alongside the document registration.

Defaults applied

When the method is called, the document is registered with the following out of the box (no callback configuration required):

  • AddOpenApi(documentName) to register the document with Microsoft.AspNetCore.OpenApi.
  • A ShouldInclude predicate that filters endpoints by [MapToApi(documentName)].
  • CreateSchemaReferenceId set to UmbracoSchemaIdGenerator.CreateSchemaReferenceId, applying Umbraco naming conventions to types under the Umbraco.Cms namespace and falling back to the framework default for everything else.
  • TagActionsByGroupNameTransformer registered as both an operation and document transformer to tag operations by API group name and clean up unused tags.
  • SortTagsAndPathsTransformer registered as a document transformer for stable, diffable output.
  • Registration in the OpenAPI UI document selector dropdown (call ExcludeFromUi() to opt out).

Note

Open to suggestions on other things we should include here. For example, I wasn't sure whether UmbracoOperationIdTransformer was applicable to extensions or just our own APIs? (of course even if we add it, extensions can always override it.

Builder methods

BackOfficeOpenApiDocumentBuilder (in Umbraco.Cms.Api.Common.OpenApi):

  • WithTitle(string title) — sets the document's Info.Title (also used as the default UI dropdown label).
  • WithUiTitle(string uiTitle) — overrides only the UI dropdown label, leaving Info.Title untouched.
  • ExcludeFromUi() — suppresses registration in the UI document selector dropdown.
  • ConfigureOpenApiOptions(Action<OpenApiOptions> configure) — adds a configuration callback for OpenApiOptions. Composes — multiple calls run in order after Umbraco's defaults.
  • WithJsonOptions(string jsonOptionsName) — convenience for resolving a registered named Microsoft.AspNetCore.Http.Json.JsonOptions via IOptionsMonitor.
  • WithJsonOptions(JsonOptions jsonOptions) — sets a caller-supplied Microsoft.AspNetCore.Http.Json.JsonOptions instance.
  • WithJsonOptions(Func<IServiceProvider, JsonOptions> factory) — sets a factory invoked when the schema service is first resolved.

BackOfficeOpenApiDocumentBuilderExtensions (in Umbraco.Cms.Api.Management.OpenApi):

  • WithBackOfficeAuthentication() — adds backoffice OAuth2 security requirements to the document.

Usage

Minimal:

builder.AddBackOfficeOpenApiDocument("my-api");

Typical (extension template):

builder.AddBackOfficeOpenApiDocument("my-api", document => document
    .WithTitle("My API")
    .WithBackOfficeAuthentication()
    .ConfigureOpenApiOptions(options =>
    {
        options.AddDocumentTransformer((doc, _, _) =>
        {
            doc.Info.Version = "1.0";
            return Task.CompletedTask;
        });
    }));

Opt out of the UI dropdown (e.g. machine-only docs):

builder.AddBackOfficeOpenApiDocument("my-api", document => document
    .WithTitle("My API")
    .ExcludeFromUi());

Override only the UI dropdown label:

builder.AddBackOfficeOpenApiDocument("my-api", document => document
    .WithTitle("My Long API Title For The Document Info")
    .WithUiTitle("Short label"));

Align schema serialization with a registered named JsonOptions:

builder.AddBackOfficeOpenApiDocument("my-api", document => document
    .WithJsonOptions(Constants.JsonOptionsNames.BackOffice));

Testing

Scaffold a project from the Umbraco extension template (which uses the new helper out of the box) and run the site. Open /umbraco/openapi in the backoffice and confirm the extension's document appears in the dropdown alongside Default / Management / Delivery, with the WithTitle value as the label and the [MapToApi("my-api")] controllers rendered under it. Then exercise each builder method:

  • WithTitle — change the title in the composer, restart, and confirm the document's Info.Title (visible at /umbraco/openapi/{documentName}.json and in the UI header) and the dropdown label both update.
  • WithUiTitle — set it to something different from WithTitle; the UI dropdown should show the WithUiTitle value while Info.Title continues to reflect WithTitle.
  • ExcludeFromUi — add the call; the document should disappear from the dropdown but remain reachable at /umbraco/openapi/{documentName}.json.
  • ConfigureOpenApiOptions — add an AddDocumentTransformer that mutates the document (e.g. doc.Info.Description = "...") and confirm it shows up in the JSON. Verify multiple ConfigureOpenApiOptions calls compose by adding two transformers and observing both effects.
  • WithJsonOptions(string) — register a named Microsoft.AspNetCore.Http.Json.JsonOptions via services.Configure<JsonOptions>("my-name", ...), then call .WithJsonOptions("my-name"); confirm the schema reflects those options.
  • WithJsonOptions(JsonOptions) — pass a JsonOptions instance with a clear deviation from the default (e.g. PropertyNamingPolicy = null for PascalCase) and confirm the schema property names in the JSON reflect that change.
  • WithJsonOptions(Func<>) — pass sp => sp.GetRequiredService<IOptionsMonitor<JsonOptions>>().Get("name") and confirm the schema reflects the resolved named options.
  • WithBackOfficeAuthentication — leave it on (it's in the template by default) and confirm the SwaggerUI prompts for backoffice authentication when invoking endpoints from the rendered docs.

Confirm the built-in Management API document still renders correctly through the migrated registration — same operation IDs, same security requirements, same schema output. The UmbracoSchemaIdGenerator.CreateSchemaReferenceId extraction is a refactor with no behaviour change for the Default and Delivery documents.

lauraneto added 2 commits May 8, 2026 09:01
Bundles AddOpenApi, the [MapToApi]-aware ShouldInclude predicate, the
Umbraco schema reference ID convention, and AddOpenApiDocumentToUi
behind a single IUmbracoBuilder.AddBackOfficeOpenApiDocument call.
Authors pass documentName, an optional title (used both as Info.Title
and the UI dropdown label), and an optional configure callback that
runs last so it can override anything the helper sets. An optional
jsonOptionsName is forwarded to ReplaceOpenApiSchemaService for
documents that need schema-time JSON serialization aligned to a named
JsonOptions.

Schema reference ID logic moves out of ConfigureUmbracoOpenApiOptionsBase
into UmbracoSchemaIdGenerator.CreateSchemaReferenceId so both the new
helper and the base class share one source of truth. The extension
template's composer collapses to a single AddBackOfficeOpenApiDocument
call, with document Info.Version, backoffice security, and the operation
ID transformer staying in the configure callback.
Replace the parameter-list AddBackOfficeOpenApiDocument helper with a
callback-based form that yields a BackOfficeOpenApiDocumentBuilder. The
builder owns its state and applies it to the IUmbracoBuilder once the
user callback returns, so authors don't need to remember a terminal
Build call. Extension methods can layer on (e.g.
WithBackOfficeAuthentication in Umbraco.Cms.Api.Management) without the
core helper carrying every opinion.

Defaults stay sensible: filtering by [MapToApi(documentName)], the
Umbraco schema reference IDs, and the tag/sort transformers that v17's
global Swashbuckle pipeline applied. UI dropdown registration is
opt-out via ExcludeFromUi rather than opt-in. JSON options for schema
generation are an opt-in via WithHttpJsonOptions (instance or factory),
described purely in terms of the schema effect.

Move UmbracoSchemaIdGenerator's CreateSchemaReferenceId wrapper out of
ConfigureUmbracoOpenApiOptionsBase so both the base config class and
the new builder share one source of truth, and update the
ContentTypeSchemaTransformer / unit test callsites accordingly. Refresh
the extension template to use the new shape.
@lauraneto lauraneto changed the title Add fluent builder for registering custom backoffice OpenAPI documents Open API: Add fluent builder for registering custom backoffice OpenAPI documents May 8, 2026
lauraneto added 4 commits May 8, 2026 15:00
The Http qualifier was naming the .NET type rather than the intent.
The parameter type carries the disambiguation; the method name is now
intent-focused and the XML doc explains the use case (matching the
serialization conventions of the API endpoints the document describes).
Convenience overload that accepts the registered name and resolves the
matching Microsoft.AspNetCore.Http.Json.JsonOptions via IOptionsMonitor.
Documents on all three WithJsonOptions overloads now explicitly name
the HTTP JsonOptions type so consumers know which framework type they
are configuring.
…cument

Replaces the AddUmbracoOpenApiDocument<ConfigureUmbracoManagementApiOpenApiOptions>
call with the new fluent builder. The custom config class becomes dead
code and is deleted; all per-document opinions (Info metadata, security
requirements, transformers, JSON options) move into the configuration
callback alongside the document registration.

Behavior preserved: same ShouldInclude (now via [MapToApi]-only since
all Management controllers carry the attribute through their base class),
same schema reference IDs, same operation IDs via UmbracoOperationIdTransformer,
same backoffice security requirements, same schema/operation transformers,
same named JSON options for schema generation.
@lauraneto lauraneto marked this pull request as ready for review May 11, 2026 07:05
Copilot AI review requested due to automatic review settings May 11, 2026 07:05
@claude

claude Bot commented May 11, 2026

Copy link
Copy Markdown

Claude finished @lauraneto's task in 5m 55s —— View job


PR Review

Target: origin/release/18.0 · Based on commit: efc902cb
· Skipped: 0 noise files out of 12 total

Introduces IUmbracoBuilder.AddBackOfficeOpenApiDocument(...) — a fluent builder for registering custom OpenAPI documents with Umbraco's defaults — and migrates the built-in Management API registration to use it, removing ConfigureUmbracoManagementApiOpenApiOptions.

  • Modified public API: UmbracoSchemaIdGenerator gains new internal static CreateSchemaReferenceId(JsonTypeInfo); OpenApiServiceCollectionExtensions.AddOpenApiDocumentToUi gains a new internal overload; OpenApiSchemaServiceExtensions.ReplaceOpenApiSchemaService gains a new public factory overload
  • New public API: BackOfficeOpenApiDocumentBuilder (public sealed class), UmbracoBuilderOpenApiExtensions.AddBackOfficeOpenApiDocument, BackOfficeOpenApiDocumentBuilderExtensions.WithBackOfficeAuthentication
  • Breaking changes: None — deleted ConfigureUmbracoManagementApiOpenApiOptions was internal; moved CreateSchemaReferenceId remains internal
  • Other changes: The Management API document registration now uses AddBackOfficeOpenApiDocument; the extension template UmbracoExtensionApiComposer.cs is updated to the new API; the template's API title is silently corrected from "Umbraco ExtensionBackoffice API" (missing space) to "Umbraco Extension Backoffice API"

Note

Complexity advisory — This PR may benefit from splitting.

  • Mixed intent: Adds AddBackOfficeOpenApiDocument (new feature) and migrates the Management API to use it (refactor) across 3 projects. The migration is a natural validation of the new API, but consider splitting so the new helper can be reviewed in isolation from the Management API migration — especially since the migration changes the effective ShouldInclude logic (see Important finding below).

This is an observation, not a blocker. The full review follows below.


Important

  • src/Umbraco.Cms.Api.Common/OpenApi/BackOfficeOpenApiDocumentBuilder.cs + src/Umbraco.Cms.Api.Common/OpenApi/UmbracoBuilderOpenApiExtensions.cs: No tests added for the new public API surface. The test file change only re-points 4 existing calls to UmbracoSchemaIdGenerator.CreateSchemaReferenceId; the builder's behaviour (all WithTitle/WithUiTitle/ExcludeFromUi/ConfigureOpenApiOptions/WithJsonOptions paths, the composition ordering, AddBackOfficeOpenApiDocument itself) is completely untested. Per the project's coding preferences, new application services require at least integration tests. Fix this →

  • src/Umbraco.Cms.Api.Common/OpenApi/BackOfficeOpenApiDocumentBuilder.cs:123-124: The new ShouldInclude predicate uses only HasMapToApiAttribute(DocumentName), whereas the old ConfigureUmbracoOpenApiOptionsBase.ShouldInclude had a second condition: apiVersionMetadata.Name == ApiName. For the Management API this is safe because all controllers inherit from ManagementApiControllerBase which carries [MapToApi]. But the change in behaviour should be verified — any Management API endpoint that historically relied on the apiVersionMetadata.Name path would now silently disappear from the document. A comment explaining why the second condition was intentionally dropped (and confirming all Management API endpoints carry [MapToApi]) would help future reviewers. Fix this →


Suggestions

  • src/Umbraco.Cms.Api.Common/OpenApi/UmbracoSchemaIdGenerator.cs:40: CreateSchemaReferenceId is internal. Extension package authors who want to replicate Umbraco's full schema ID behaviour (including returning null for types that should be inlined) for a manually-configured document can't call this method. UmbracoSchemaIdGenerator.Generate(Type) is public but lacks that guard. If this method is part of the promised extension contract, making it public would complete the story.

  • src/Umbraco.Cms.Api.Common/OpenApi/BackOfficeOpenApiDocumentBuilder.cs:106-110: The WithJsonOptions overloads replace each other (last call wins) rather than composing like ConfigureOpenApiOptions. This is the right semantic, but the XML doc doesn't mention it. Users who chain multiple WithJsonOptions calls will be surprised to find only the last one takes effect. A one-liner in the doc would prevent confusion.

  • src/Umbraco.Cms.Api.Common/OpenApi/UmbracoBuilderOpenApiExtensions.cs / PR description: UmbracoOperationIdTransformer is NOT included in the builder's defaults (unlike the old ConfigureUmbracoOpenApiOptionsBase, which always added it). The Management API compensates by adding it explicitly in ConfigureOpenApiOptions. Extension authors following the template won't get Umbraco-style operation IDs automatically. A mention in the XML doc of AddBackOfficeOpenApiDocument (e.g. noting the transformer is available via ConfigureOpenApiOptions) would improve discoverability.


Approved with Suggestions

Good to go, but please carefully consider the importance of the suggestions. The missing tests for BackOfficeOpenApiDocumentBuilder are the most significant item — this is new public API surface that will be used by extension authors and deserves test coverage before shipping.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a fluent, backoffice-focused OpenAPI document registration helper (IUmbracoBuilder.AddBackOfficeOpenApiDocument(...)) that applies Umbraco defaults (endpoint filtering, schema ID naming, stable sorting/tagging, optional UI registration), and migrates the built-in Management API OpenAPI registration to use the new builder-based approach.

Changes:

  • Added AddBackOfficeOpenApiDocument + BackOfficeOpenApiDocumentBuilder for fluent registration/configuration of custom backoffice OpenAPI documents.
  • Migrated Management API OpenAPI registration to the new builder (and removed ConfigureUmbracoManagementApiOpenApiOptions).
  • Extracted/centralized schema reference ID generation into UmbracoSchemaIdGenerator.CreateSchemaReferenceId and updated call sites/tests accordingly.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Umbraco.Tests.UnitTests/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoOpenApiOptionsBaseTests.cs Updates tests to validate schema reference IDs via UmbracoSchemaIdGenerator.CreateSchemaReferenceId.
templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs Updates the extension template to register its OpenAPI doc via the new builder and management auth helper.
src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeOpenApiDocumentBuilderExtensions.cs Adds management-specific fluent builder extension for backoffice authentication requirements.
src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs Migrates Management API OpenAPI document registration to AddBackOfficeOpenApiDocument with per-document configuration.
src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiOpenApiOptions.cs Removes the old named-options-based Management API OpenAPI configuration class.
src/Umbraco.Cms.Api.Delivery/OpenApi/Transformers/ContentTypeSchemaTransformer.cs Switches schema ID generation to UmbracoSchemaIdGenerator.CreateSchemaReferenceId.
src/Umbraco.Cms.Api.Common/OpenApi/UmbracoSchemaIdGenerator.cs Adds CreateSchemaReferenceId(JsonTypeInfo) to apply Umbraco schema naming + framework fallback.
src/Umbraco.Cms.Api.Common/OpenApi/UmbracoBuilderOpenApiExtensions.cs Adds the public IUmbracoBuilder.AddBackOfficeOpenApiDocument(...) entrypoint.
src/Umbraco.Cms.Api.Common/OpenApi/BackOfficeOpenApiDocumentBuilder.cs Introduces the fluent builder implementing defaults, UI inclusion/exclusion, and optional schema JSON options alignment.
src/Umbraco.Cms.Api.Common/DependencyInjection/OpenApiServiceCollectionExtensions.cs Adds a lazy-title overload for UI dropdown registration and routes the public overload through it.
src/Umbraco.Cms.Api.Common/DependencyInjection/OpenApiSchemaServiceExtensions.cs Adds a factory-based overload for replacing the internal OpenAPI schema service’s JsonOptions source.
src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoOpenApiOptionsBase.cs Uses UmbracoSchemaIdGenerator.CreateSchemaReferenceId and removes the duplicated schema-ID helper implementation.

Comment thread src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs Outdated

@AndyButland AndyButland left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me @lauraneto. I've played around with the different options for the AddBackOfficeOpenApiDocument builder methods and am seeing the expected results at /umbraco/openapi/index.html.

On the question of including UmbracoOperationIdTransformer, I think it makes sense to do so. The developer using AddBackOfficeOpenApiDocument is already opting into Umbraco's defaults, so you could argue it makes sense to align this too. If you do include it, would just suggest to make sure the XML docs for AddBackOfficeOpenApiDocument are detailed enough to explain what you are opting into by using it. E.g. include something like "Operation IDs are generated by UmbracoOperationIdTransformer by default; register your own IOpenApiOperationTransformer via ConfigureOpenApiOptions to override."

The inline point about unit tests for the builder class would be good to look at too.

Approving, but please have a look over the above and decide which way you want to go before merging in.

And of course we need to make sure the details of this are documented on https://docs.umbraco.com too (can be added to your existing PR).

Make UmbracoOperationIdTransformer part of the builder's defaults instead of
the Management API adding it explicitly, and expand the XML docs on
AddBackOfficeOpenApiDocument to spell out the defaults a caller opts into.

Add tests covering the new builder and its defaults:
- Unit tests for BackOfficeOpenApiDocumentBuilder defaults (CreateSchemaReferenceId,
  ShouldInclude, ConfigureOpenApiOptions composition, WithTitle/WithUiTitle UI
  dropdown handling, ExcludeFromUi).
- Integration tests that register sample controllers, fetch the generated OpenAPI
  document and verify the defaults end-to-end: Info.Title from WithTitle,
  MapToApi filtering, Umbraco operation-id and schema-id conventions (including
  the version-suffix branch), tag-by-group-name and tag-first path sorting.
- Integration tests for the three WithJsonOptions overloads (instance, factory,
  named) confirming the configured JsonOptions reach schema generation.
@lauraneto lauraneto requested a review from AndyButland May 11, 2026 16:06
lauraneto added 3 commits May 11, 2026 18:35
UmbracoOperationIdTransformer is now part of the AddBackOfficeOpenApiDocument
defaults, so the template's custom action-name transformer would only overwrite
the work the default just did. Drop it, and consolidate the documentation
pointer to a single link.
…it in AddBackOfficeOpenApiDocument

Filter only removes redundant JSON-equivalent MIME types (text/json,
application/*+json, text/plain) when application/json is present.
Non-JSON types like application/xml are preserved. Register the
transformer as a default in AddBackOfficeOpenApiDocument so custom
backoffice documents get the same treatment as Umbraco's own APIs.

@AndyButland AndyButland left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work on adding the tests here @lauraneto. I found a few small things further to note, but once they are handled then I think this is all good to merge in.

Comment thread src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs Outdated
Comment thread src/Umbraco.Cms.Api.Common/OpenApi/MimeTypesTransformer.cs
- Drop RequireNonNullablePropertiesSchemaTransformer and MimeTypesTransformer
  from the Management API's ConfigureOpenApiOptions block — both are now
  defaults on the builder.
- Expand MimeTypesTransformer XML docs to reflect its broader role (it now
  applies to every backoffice document, not just the Management API) and
  correct the response-side inline comment.
- Move MimeTypesTransformerTests from the Delivery test folder/namespace to
  the Api.Common test folder/namespace, since the transformer is no longer
  Delivery-specific.
- Rename BackOfficeOpenApiDocumentExtensionTests to
  UmbracoBuilderOpenApiExtensionsTests so the test fixture name matches the
  concrete class under test.
@lauraneto lauraneto merged commit f007855 into release/18.0 May 12, 2026
22 of 23 checks passed
@lauraneto lauraneto deleted the v18/feature/add-back-office-open-api-document-extension branch May 12, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants