Skip to content

readOnly schema attribute rendering in request/response bodies depends on expansion order #11010

Open

Description

@cmilatinov

Q&A

  • OS: Windows
  • Browser: Chrome
  • Version: 151
  • Method of installation: npm
  • Swagger-UI version: 5.32.13
  • Swagger/OpenAPI version: OpenAPI 3.1.0

Content & configuration

Read-only (readOnly: true) property visibility in Swagger UI is not computed per operation/context. Instead it behaves like a single shared/global state that is set by the order in which operations are expanded, and it applies to every operation — even operations that reference different component schemas.

Example Swagger/OpenAPI definition:

{
  "openapi": "3.1.0",
  "info": {
    "title": "Read-only repro",
    "version": "1.0.0",
    "description": "Minimal spec to reproduce Swagger UI read-only rendering. A single shared schema (Widget) with one readOnly property (id) and one regular property (name) is referenced by a POST request body and a GET response. Expected: the POST body hides `id` (read-only), the GET response shows it."
  },
  "paths": {
    "/widgets": {
      "post": {
        "summary": "Create widget (request body — read-only `id` should be HIDDEN)",
        "operationId": "createWidget",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/Widget" }
            }
          }
        },
        "responses": {
          "201": { "description": "Created" }
        }
      }
    },
    "/widgets/{widgetId}": {
      "get": {
        "summary": "Get widget (response — read-only `id` should be SHOWN)",
        "operationId": "getWidget",
        "parameters": [
          {
            "name": "widgetId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Widget" }
              }
            }
          }
        }
      }
    },
    "/gadgets": {
      "post": {
        "summary": "Create gadget (separate schema — read-only `serial` should be HIDDEN)",
        "operationId": "createGadget",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/Gadget" }
            }
          }
        },
        "responses": {
          "201": { "description": "Created" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Widget": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name"],
        "properties": {
          "name": {
            "type": "string",
            "description": "Regular writable attribute."
          },
          "id": {
            "type": "string",
            "readOnly": true,
            "description": "Read-only attribute — expected HIDDEN in POST body, SHOWN in GET response."
          }
        }
      },
      "Gadget": {
        "type": "object",
        "additionalProperties": false,
        "required": ["label"],
        "properties": {
          "label": {
            "type": "string",
            "description": "Regular writable attribute."
          },
          "serial": {
            "type": "string",
            "readOnly": true,
            "description": "Read-only attribute on a separate schema — expected HIDDEN in the POST body."
          }
        }
      }
    }
  }
}

Swagger-UI configuration options:

SwaggerUI({
  dom_id: '#swagger-ui-container',
  url: '<url to spec endpoint>',
})

Describe the bug you're encountering

Swagger UI 5.32.13 (OpenAPI 3.1) computes readOnly visibility as a single global state instead of per operation. The first operation expanded after page load sets read-only visibility for every operation:

  • Expand a GET (response) first → readOnly fields are shown everywhere, including POST request bodies (wrong).
  • Expand a POST (request) first → readOnly fields are hidden everywhere, including GET responses (wrong).

This leaks across unrelated schemas too: expanding POST /gadgets (schema Gadget) first, then GET /widgets (schema Widget), removes the read-only field from the GET response even though they share no schema.

To reproduce...

  1. Load the spec in Swagger UI. All operations collapsed by default.
  2. Case A:
    i. expand GET /widgets/{widgetId} first,
    ii. then expand POST /widgets and POST /gadgets. → id and serial appear in the POST request bodies (should be hidden).
  3. Reload. Case B:
    i. expand POST /widgets first,
    ii. then expand GET /widgets/{widgetId}. → id is missing from the GET response model (should be shown).
  4. Cross-schema:
    i. expand POST /gadgets (schema Gadget) first
    ii. then expand GET /widgets/{widgetId} (schema Widget). → the read-only id is removed from the GET response, even though the two operations use different schemas.

Expected behavior

Read-only visibility must be computed independently per rendered model, from that operation's own context:

  • Request body models → hide readOnly properties.
  • Response models → show readOnly properties.
  • The result must not depend on expansion order, nor on other operations, nor on other schemas.

Screenshots

Expanding GET first:
Image
Then POST with GET already expanded:
Image
The readOnly attribute is shown in both.

Expanding POST first:
Image
Then GET with POST already expanded:
Image
The readOnly attribute is hidden in both.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions