From 957a9d33028221ab505c330e30cbf65a315e23f7 Mon Sep 17 00:00:00 2001 From: John Letey Date: Sat, 9 May 2026 12:28:15 +0100 Subject: [PATCH 1/4] feat: support customizing the Scalar docs renderer config --- api.go | 19 ++++++++++++++++++- api_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/api.go b/api.go index a8039bf4..d2e4577e 100644 --- a/api.go +++ b/api.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "html" "io" "mime/multipart" "net/http" @@ -205,6 +206,13 @@ type Config struct { // route altogether. DocsRenderer string + // DocsRendererConfig is optional renderer-specific config that gets + // JSON-marshaled into the docs HTML. Only the Scalar renderer uses it, + // where it becomes the value of Scalar's `data-configuration` attribute. + // See https://github.com/scalar/scalar/blob/main/documentation/configuration.md + // for the available options. + DocsRendererConfig any + // SchemasPath is the path to the API schemas. If set to `/schemas` it will // allow clients to get `/schemas/{schema}` to view the schema in a browser // or for use in editors like VSCode to provide autocomplete & validation. @@ -640,6 +648,15 @@ func (a *api) registerDocsRoute() { "style-src 'unsafe-inline'", // TODO: Somehow drop 'unsafe-inline' } + var configAttr string + if a.config.DocsRendererConfig != nil { + b, err := json.Marshal(a.config.DocsRendererConfig) + if err != nil { + panic("failed to marshal DocsRendererConfig: " + err.Error()) + } + configAttr = ` data-configuration="` + html.EscapeString(string(b)) + `"` + } + body = []byte(` @@ -649,7 +666,7 @@ func (a *api) registerDocsRoute() { ` + title + ` - + `) diff --git a/api_test.go b/api_test.go index 87eb3b76..3735247a 100644 --- a/api_test.go +++ b/api_test.go @@ -216,6 +216,43 @@ func TestDocsRenderers(t *testing.T) { assert.Contains(t, resp.Body.String(), "@scalar/api-reference") }) + t.Run("ScalarRendererConfig", func(t *testing.T) { + _, api := humatest.New(t, huma.Config{ + OpenAPI: &huma.OpenAPI{ + Info: &huma.Info{Title: "Test API", Version: "1.0.0"}, + }, + DocsPath: "/docs", + DocsRenderer: huma.DocsRendererScalar, + DocsRendererConfig: map[string]any{ + "theme": "purple", + "layout": "modern", + }, + OpenAPIPath: "/openapi", + Formats: huma.DefaultFormats, + }) + + resp := api.Get("/docs") + assert.Equal(t, http.StatusOK, resp.Code) + assert.Contains(t, resp.Body.String(), `data-configuration="`) + assert.Contains(t, resp.Body.String(), `"theme":"purple"`) + assert.Contains(t, resp.Body.String(), `"layout":"modern"`) + }) + + t.Run("ScalarRendererConfigInvalid", func(t *testing.T) { + assert.Panics(t, func() { + humatest.New(t, huma.Config{ + OpenAPI: &huma.OpenAPI{ + Info: &huma.Info{Title: "Test API", Version: "1.0.0"}, + }, + DocsPath: "/docs", + DocsRenderer: huma.DocsRendererScalar, + DocsRendererConfig: make(chan int), + OpenAPIPath: "/openapi", + Formats: huma.DefaultFormats, + }) + }) + }) + t.Run("SwaggerUIRenderer", func(t *testing.T) { _, api := humatest.New(t, huma.Config{ OpenAPI: &huma.OpenAPI{ From aaa0c2e44ff30117eef99c26b1c6956a80390822 Mon Sep 17 00:00:00 2001 From: John Letey Date: Thu, 28 May 2026 16:47:00 +0200 Subject: [PATCH 2/4] feat: support customizing the SwaggerUI docs renderer config --- api.go | 35 +++++++++++++++++++++++++++-------- api_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/api.go b/api.go index d2e4577e..eeffec40 100644 --- a/api.go +++ b/api.go @@ -206,11 +206,18 @@ type Config struct { // route altogether. DocsRenderer string - // DocsRendererConfig is optional renderer-specific config that gets - // JSON-marshaled into the docs HTML. Only the Scalar renderer uses it, - // where it becomes the value of Scalar's `data-configuration` attribute. - // See https://github.com/scalar/scalar/blob/main/documentation/configuration.md - // for the available options. + // DocsRendererConfig is an optional renderer-specific config. When set, it is + // JSON-marshaled into the docs HTML. Scalar and SwaggerUI use it, Stoplight + // Elements ignores it. + // + // Scalar reads it from the `data-configuration` attribute. See + // https://github.com/scalar/scalar/blob/main/documentation/configuration.md + // for the options. + // + // SwaggerUI merges its fields into the SwaggerUIBundle config object. See + // https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/ + // for the options. Huma owns the `url` and `dom_id` fields, so setting + // them here does nothing. DocsRendererConfig any // SchemasPath is the path to the API schemas. If set to `/schemas` it will @@ -717,10 +724,19 @@ func (a *api) registerDocsRoute() { "form-action 'none'", "frame-ancestors 'none'", "sandbox allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox", - "script-src https://unpkg.com/swagger-ui-dist@5.31.1/swagger-ui-bundle.js 'sha256-loGQL86SKUDRkBgfqt+XGmcml9Plihleifquht4CLYE='", + "script-src https://unpkg.com/swagger-ui-dist@5.31.1/swagger-ui-bundle.js 'sha256-gRya58TMnKTH/Tne/zBInjBwFUxL66aMDYvPuAX0lNY='", "style-src https://unpkg.com/swagger-ui-dist@5.31.1/swagger-ui.css", } + var configAttr string + if a.config.DocsRendererConfig != nil { + b, err := json.Marshal(a.config.DocsRendererConfig) + if err != nil { + panic("failed to marshal DocsRendererConfig: " + err.Error()) + } + configAttr = ` data-config="` + html.EscapeString(string(b)) + `"` + } + body = []byte(` @@ -733,10 +749,13 @@ func (a *api) registerDocsRoute() {
- - - -`)) -}) +api := humachi.New(router, config) ``` ![Scalar Docs](./scalar.png) ### Stoplight Elements -You can customize the default docs by providing your own HTML so you can set the layout, styles, colors, etc as needed. +[Stoplight Elements](https://stoplight.io/open-source/elements) is the default renderer, so you get it without setting `config.DocsRenderer` at all. It doesn't read `config.DocsRendererConfig`. ```go title="code.go" router := chi.NewRouter() config := huma.DefaultConfig("Docs Example", "1.0.0") -config.DocsPath = "" api := humachi.New(router, config) - -router.Get("/docs", func(w http.ResponseWriter, r *http.Request) { - // Please refer to the "DocsRendererStoplightElements" renderer code inside api.go on what to return here -}) ``` ![Stoplight Elements Stacked](./elements-stacked.png) @@ -96,13 +71,16 @@ router.Get("/docs", func(w http.ResponseWriter, r *http.Request) { ```go title="code.go" router := chi.NewRouter() config := huma.DefaultConfig("Docs Example", "1.0.0") -config.DocsPath = "" +config.DocsRenderer = huma.DocsRendererSwaggerUI -api := humachi.New(router, config) +// Optional. These fields are merged into the SwaggerUIBundle config object. See +// https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/ +config.DocsRendererConfig = map[string]any{ + "defaultModelsExpandDepth": -1, // hide the models section + "tryItOutEnabled": true, // enable "Try it out" by default +} -router.Get("/docs", func(w http.ResponseWriter, r *http.Request) { - // Please refer to the "DocsRendererSwaggerUI" renderer code inside api.go on what to return here -}) +api := humachi.New(router, config) ``` ![SwaggerUI](./swaggerui.png) From 8ab93aa9629651be858e7ba28642b1c729efc8d8 Mon Sep 17 00:00:00 2001 From: John Letey Date: Thu, 28 May 2026 17:48:10 +0200 Subject: [PATCH 4/4] docs: dedupe custom renderer guidance --- docs/docs/features/api-docs.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/docs/features/api-docs.md b/docs/docs/features/api-docs.md index cdf45ecc..cb637098 100644 --- a/docs/docs/features/api-docs.md +++ b/docs/docs/features/api-docs.md @@ -18,7 +18,7 @@ You can switch to other documentation renderers using `config.DocsRenderer`. The !!! info "Disabling the Docs" - You can disable the built-in documentation by setting `config.DocsPath` to an empty string. This allows you to provide your own documentation renderer if you wish. + You can disable the built-in documentation by setting `config.DocsPath` to an empty string, then register your own route on the underlying router. The `DocsRenderer*` functions in [`api.go`](https://github.com/danielgtaylor/huma/blob/main/api.go) show what to return. !!! warning "Middleware Conflicts" @@ -26,9 +26,7 @@ You can switch to other documentation renderers using `config.DocsRenderer`. The ## Customizing Documentation -Each renderer takes its own options through `config.DocsRendererConfig`. Set it to any value that marshals to JSON (a `map[string]any` is easiest), and Huma writes it into the docs HTML. Scalar and SwaggerUI support it; Stoplight Elements ignores it. - -If you'd rather control the HTML yourself, set `config.DocsPath` to an empty string to turn off the built-in docs, then register your own `/docs` route on the underlying router. The `DocsRenderer*` functions in [`api.go`](https://github.com/danielgtaylor/huma/blob/main/api.go) show what to return. +Each renderer accepts its own options through `config.DocsRendererConfig`. Set it to any value that marshals to JSON (a `map[string]any` is easiest), and Huma writes it into the docs HTML. Scalar and SwaggerUI support it; Stoplight Elements ignores it. ### Scalar Docs