swagger-api / swagger-api/swagger-ui
readOnly schema attribute rendering in request/response bodies depends on expansion order
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 29k
- Forks
- 9.3k
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 25
Description
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Load the supplied OpenAPI 3.1 definition in Swagger UI and reproduce both expansion orders, including the cross-schema case. Trace the model-rendering path responsible for readOnly visibility and verify that request bodies hide those properties while responses show them independently; done means the result no longer changes with expansion order or other operations.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- documentation, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100