Swagger, now OpenAPI, can describe both JSON and form-encoded request bodies, and it’s surprisingly flexible about how you structure them.
Let’s say you’re building an API endpoint to create a new user. You might want to accept this data as JSON:
{
"username": "jane.doe",
"email": "jane.doe@example.com",
"isActive": true
}
Or, you might want to accept it as form data, especially if it’s coming from a traditional HTML form:
username=jane.doe&email=jane.doe@example.com&isActive=true
OpenAPI can describe both of these scenarios clearly.
Here’s how you’d define the JSON request body in an OpenAPI specification:
paths:
/users:
post:
summary: Create a new user
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
username:
type: string
example: "jane.doe"
email:
type: string
format: email
example: "jane.doe@example.com"
isActive:
type: boolean
default: true
example: true
required:
- username
- email
responses:
'201':
description: User created successfully
The key here is the requestBody object. Inside content, we specify the application/json media type. The schema under that describes the structure of the JSON object itself, detailing its properties, their types, and which ones are mandatory.
Now, if you wanted to support the form-encoded version, you’d add another entry under content:
paths:
/users:
post:
summary: Create a new user
requestBody:
required: true
content:
application/json: # Existing JSON definition
schema:
type: object
properties:
username:
type: string
example: "jane.doe"
email:
type: string
format: email
example: "jane.doe@example.com"
isActive:
type: boolean
default: true
example: true
required:
- username
- email
application/x-www-form-urlencoded: # New form data definition
schema:
type: object
properties:
username:
type: string
example: "jane.doe"
email:
type: string
format: email
example: "jane.doe@example.com"
isActive:
type: boolean
default: true
example: true
required:
- username
- email
responses:
'201':
description: User created successfully
Notice that for application/x-www-form-urlencoded, the schema is still an object with properties. OpenAPI intelligently understands that for form data, these properties map directly to key-value pairs in the application/x-www-form-urlencoded payload. The required fields also apply to the form data keys.
You can even define multipart/form-data for file uploads or complex form submissions. The structure is similar, but you’ll often use type: string with format: base64 or reference external file schemas.
The real power comes from defining these multiple content types. A client can then inspect the OpenAPI spec and choose the format it prefers or is capable of sending. If an API supports both application/json and application/x-www-form-urlencoded for the same endpoint, the requestBody section will list both under content.
When you’re defining these schemas, remember that example values are incredibly useful for documentation and for clients trying to generate sample requests. They show exactly what a valid payload looks like for each media type.
The choice between JSON and form data often depends on the context of the client. Browser-based forms typically submit application/x-www-form-urlencoded or multipart/form-data. Modern JavaScript clients or server-to-server communication often prefer application/json. By supporting both, you make your API more versatile.
What’s often overlooked is how deeply nested structures can be represented in form data. While simple key-value pairs are common, complex, nested objects can be flattened into a single string using dot notation or similar conventions. OpenAPI’s schema for application/x-www-form-urlencoded can describe this, though the actual encoding convention needs to be understood by both client and server.