From 0fe916b094423290eeb241c9c1a297ed526bd4a1 Mon Sep 17 00:00:00 2001 From: suedeapple Date: Tue, 30 Jun 2026 13:47:33 +0100 Subject: [PATCH 1/3] Update API controllers and OpenAPI config for Clean Starter - Hide PackageController from API explorer - Remove TestController and its implementation - Add [Tags] and MapToApi attributes to Contact, Dictionary, and Search controllers for improved OpenAPI grouping - Restrict OpenAPI doc to controllers with MapToApi("clean-starter") --- .../Controllers/PackageController.cs | 2 +- .../Clean.Blog/Controllers/TestController.cs | 43 ------------------- .../Controllers/ContactV1Controller.cs | 1 + .../Controllers/DictionaryApiV1Controller.cs | 1 + .../Controllers/SeachApiV1Controller.cs | 1 + .../Startup/CleanStarterOpenApiExtensions.cs | 11 +++++ 6 files changed, 15 insertions(+), 44 deletions(-) delete mode 100644 template/Clean.Blog/Controllers/TestController.cs diff --git a/template/Clean.Blog/Controllers/PackageController.cs b/template/Clean.Blog/Controllers/PackageController.cs index 674ead37..63ea93ec 100644 --- a/template/Clean.Blog/Controllers/PackageController.cs +++ b/template/Clean.Blog/Controllers/PackageController.cs @@ -13,7 +13,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Package.Created; [Route("api/v{version:apiVersion}/package")] [ApiVersion("1.0")] -[ApiExplorerSettings(GroupName = "Package")] +[ApiExplorerSettings(GroupName = "Package", IgnoreApi = true)] [MapToApi("clean-starter")] [ApiController] public class PackageController : CreatedPackageControllerBase diff --git a/template/Clean.Blog/Controllers/TestController.cs b/template/Clean.Blog/Controllers/TestController.cs deleted file mode 100644 index 5c1646b8..00000000 --- a/template/Clean.Blog/Controllers/TestController.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Asp.Versioning; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Common.Attributes; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; - -namespace Umbraco.Cms.Api.Management.Controllers.Package.Created; - -[Route("api/v{version:apiVersion}/test")] -[ApiVersion("1.0")] -[MapToApi("clean-starter")] -[ApiController] -public class TestController : CreatedPackageControllerBase -{ - private readonly IDataTypeService _dataTypeService; - private readonly IEntityXmlSerializer _serializer; - - public TestController( - IDataTypeService dataTypeService, - IEntityXmlSerializer serializer) - { - _dataTypeService = dataTypeService; - _serializer = serializer; - } - - [HttpGet] - [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task GetPackage( - CancellationToken cancellationToken) - { - IDataType? dataType = await _dataTypeService.GetAsync("[BlockList] Main Content"); - if (dataType == null) - { - return NotFound("Unable to locate data type"); - } - - var output = _serializer.Serialize(dataType); - - return Ok(output); - } -} \ No newline at end of file diff --git a/template/Clean.Headless/Controllers/ContactV1Controller.cs b/template/Clean.Headless/Controllers/ContactV1Controller.cs index b96924fa..3df66b3d 100644 --- a/template/Clean.Headless/Controllers/ContactV1Controller.cs +++ b/template/Clean.Headless/Controllers/ContactV1Controller.cs @@ -15,6 +15,7 @@ namespace Clean.Headless.Controllers; [Route("api/v{version:apiVersion}/contact")] [ApiVersion("1.0")] [ApiExplorerSettings(GroupName = "Contact")] +[Tags("Contact")] [MapToApi("clean-starter")] [ApiController] public class ContactApiV1Controller : ControllerBase diff --git a/template/Clean.Headless/Controllers/DictionaryApiV1Controller.cs b/template/Clean.Headless/Controllers/DictionaryApiV1Controller.cs index 88564f2b..7617b98f 100644 --- a/template/Clean.Headless/Controllers/DictionaryApiV1Controller.cs +++ b/template/Clean.Headless/Controllers/DictionaryApiV1Controller.cs @@ -17,6 +17,7 @@ namespace Clean.Headless.Controllers; [ApiVersion("1.0")] [MapToApi("clean-starter")] [ApiExplorerSettings(GroupName = "Translation")] +[Tags("Translation")] [ApiController] public class DictionaryApiV1Controller( IDictionaryItemService dictionaryItemService, diff --git a/template/Clean.Headless/Controllers/SeachApiV1Controller.cs b/template/Clean.Headless/Controllers/SeachApiV1Controller.cs index ebb147ed..f429daa1 100644 --- a/template/Clean.Headless/Controllers/SeachApiV1Controller.cs +++ b/template/Clean.Headless/Controllers/SeachApiV1Controller.cs @@ -15,6 +15,7 @@ namespace Clean.Headless.Controllers; [Route("api/v{version:apiVersion}/search")] [ApiVersion("1.0")] [ApiExplorerSettings(GroupName = "Search")] +[Tags("Search")] [MapToApi("clean-starter")] [ApiController] public class SearchApiV1Controller : ControllerBase diff --git a/template/Clean.Headless/Startup/CleanStarterOpenApiExtensions.cs b/template/Clean.Headless/Startup/CleanStarterOpenApiExtensions.cs index a9912695..5af95b7e 100644 --- a/template/Clean.Headless/Startup/CleanStarterOpenApiExtensions.cs +++ b/template/Clean.Headless/Startup/CleanStarterOpenApiExtensions.cs @@ -1,6 +1,9 @@ +using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.OpenApi; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Common.Attributes; using Umbraco.Cms.Api.Common.DependencyInjection; namespace Clean.Headless.Startup; @@ -14,6 +17,14 @@ public static IServiceCollection AddCleanStarterOpenApi(this IServiceCollection { services.AddOpenApi(DocumentName, options => { + + // Include DictoryApiV1Controller, SearchApiV1Controller and ContactApiV1Controller in the OpenAPI document + options.ShouldInclude = apiDescription => + apiDescription.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor + && controllerActionDescriptor.EndpointMetadata + .OfType() + .Any(attribute => attribute.ApiName == DocumentName); + options.AddDocumentTransformer((document, _, _) => { document.Info.Title = DisplayName; From 563c38c68bf318cbd872a0072c7a5e5e29f01ba2 Mon Sep 17 00:00:00 2001 From: suedeapple Date: Tue, 30 Jun 2026 14:06:37 +0100 Subject: [PATCH 2/3] Update Clean Starter Kit headless API docs for Umbraco 18+ Add Delivery API schema config note for Umbraco 18+ with example. Clarify OpenAPI/Swagger endpoint paths for different Umbraco versions. Improve and relocate Next.js revalidation config section with updated guidance. --- .github/clean-headless-api.md | 57 ++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/.github/clean-headless-api.md b/.github/clean-headless-api.md index 5f87f2c2..d464b9b2 100644 --- a/.github/clean-headless-api.md +++ b/.github/clean-headless-api.md @@ -29,23 +29,7 @@ builder.CreateUmbracoBuilder() Once the Content Delivery API is enabled, the next step is to rebuild the Delivery API content index **DeliveryApiContentIndex**. This can be done using the Examine Management dashboard in the Settings section of the Umbraco Backoffice. -## Next.js Revalidation - -To enable automatic revalidation of content in Next.js applications, configure the following in your `appsettings.json`: - -```json -{ - "NextJs": { - "Revalidate": { - "Enabled": true, - "WebHookUrls": "[\"http://localhost:3000/api/revalidate\"]", - "WebHookSecret": "SOMETHING_SECRET" - } - } -} -``` -**Note**: Update the `WebHookUrls` to match your Next.js application's URL. ## Headless Frontend Example @@ -55,8 +39,27 @@ Phil Whittaker has created a complete headless Next.js frontend for this starter This implementation demonstrates how to use the Clean starter kit as a headless CMS with a modern Next.js frontend. + **Note for Umbraco 18**: DeliveryApi configuration must also include `GenerateContentTypeSchemas`: + + + ```json + { + "Umbraco": { + "CMS": { + "DeliveryApi": { + "Enabled": true, + "OpenApi": { + "GenerateContentTypeSchemas": true + } + } + } + } + } + ``` + --- + ## API Endpoints The Clean starter kit provides a suite of custom API endpoints for common functionality: @@ -69,10 +72,30 @@ The Clean starter kit provides a suite of custom API endpoints for common functi Explore and test the API endpoints using the built-in Swagger UI: -**URL**: `/umbraco/swagger/index.html?urls.primaryName=Clean%20starter%20kit` +**Umbraco 18+**: `/umbraco/openapi/` + +**Earlier versions**: `/umbraco/swagger/` This provides interactive documentation for all available API endpoints, including request/response schemas and the ability to test endpoints directly from the browser. +## Next.js Revalidation + +To enable automatic revalidation of content in Next.js applications, configure the following in your `appsettings.json`: + +```json +{ + "NextJs": { + "Revalidate": { + "Enabled": true, + "WebHookUrls": "[\"http://localhost:3000/api/revalidate\"]", + "WebHookSecret": "SOMETHING_SECRET" + } + } +} +``` + +**Note**: Update the `WebHookUrls` to match your Next.js application's URL. + --- ## Related Documentation From fedfbb09e5eda9a527f4e0cbdc1fa9721d7f3cd7 Mon Sep 17 00:00:00 2001 From: Paul Seal Date: Thu, 2 Jul 2026 13:30:49 +0100 Subject: [PATCH 3/3] Update API Explorer settings and mapping for PackageController updated to clean-starter-admin --- template/Clean.Blog/Controllers/PackageController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/template/Clean.Blog/Controllers/PackageController.cs b/template/Clean.Blog/Controllers/PackageController.cs index 63ea93ec..37fbbf32 100644 --- a/template/Clean.Blog/Controllers/PackageController.cs +++ b/template/Clean.Blog/Controllers/PackageController.cs @@ -13,8 +13,8 @@ namespace Umbraco.Cms.Api.Management.Controllers.Package.Created; [Route("api/v{version:apiVersion}/package")] [ApiVersion("1.0")] -[ApiExplorerSettings(GroupName = "Package", IgnoreApi = true)] -[MapToApi("clean-starter")] +[ApiExplorerSettings(GroupName = "Package")] +[MapToApi("clean-starter-admin")] [ApiController] public class PackageController : CreatedPackageControllerBase { @@ -175,4 +175,4 @@ public async Task GetPackage(CancellationToken cancellationToken, return PackageOperationStatusResult(result.Status); } } -} \ No newline at end of file +}