readOnly schema attribute rendering in request/response bodies depends on expansion order
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...
- Load the spec in Swagger UI. All operations collapsed by default.
- 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).
- 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).
- 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:
Then POST with GET already expanded:
The readOnly attribute is shown in both.
Expanding POST first:
Then GET with POST already expanded:
The readOnly attribute is hidden in both.
Source: swagger-api/swagger-ui