How to Convert Swagger 2.0 to OpenAPI 3 and Fix the Output

Convert Swagger 2.0 to OpenAPI 3 with swagger2openapi or converter.swagger.io, then fix what converters miss: auth, media types, examples and discriminators.

On this page

To convert Swagger 2.0 to OpenAPI 3, run npx swagger2openapi swagger.yaml -o openapi.yaml. The command reads the 2.0 file and writes an OpenAPI 3.0.0 document: host, basePath and schemes become a servers entry, definitions move to components/schemas, body and formData parameters become a requestBody, and every $ref is rewritten. That takes seconds. The output is valid, but it is a literal translation, so it keeps 2.0 workarounds you no longer need and copies schemas into places where they may be wrong. Plan a short review pass after the command: fix the security scheme, trim media types, check uploads and array parameters, then validate. This guide covers the conversion tools, a worked example with the converter's real output, and the checklist for that review.

Run swagger2openapi from the command line

swagger2openapi is an MIT-licensed npm package from the Mermade oas-kit project. The commands below were run against version 7.0.8. You can run it once with npx, or install it as a dev dependency so the version is pinned for your team.

Shellterminal
# One-off conversion (output format follows the file extension)
npx swagger2openapi swagger.yaml -o openapi.yaml

# Convert a hosted spec and print YAML to stdout
npx swagger2openapi https://petstore.swagger.io/v2/swagger.json --yaml

# Pin it in the project
npm install --save-dev swagger2openapi
npx swagger2openapi swagger.json -o openapi.json --patch
swagger2openapi writes JSON by default. An -o path ending in .yaml, or the --yaml flag, switches the output to YAML.
FlagWhat it doesWhen to use it
-o, --outfileWrites the result to a file instead of stdoutAlmost always
-y, --yamlWrites YAML instead of JSONWhen printing to stdout and you want YAML
-p, --patchFixes small errors in the source while convertingOlder 2.0 files that never passed strict validation
-w, --warnOnlyAdds x-s2o-warning extensions instead of failing on problems it cannot patchTo see every problem in one run instead of stopping at the first
-r, --resolveResolves external $ref filesSpecs split across several files
-b, --rbnameStores each body parameter's name in an extension you nameWhen generated client code used the 2.0 body parameter name
-t, --targetVersionOverrides the default target of 3.0.0Rarely; see the 3.1 note below
Flags from swagger2openapi --help, version 7.0.8.

The package also exposes a Node API, which is handy in a build script. convertObj takes the parsed 2.0 document and an options object, and resolves with the same options object; the converted document is on its openapi property.

JavaScriptconvert.js
const fs = require("node:fs");
const YAML = require("yaml");
const converter = require("swagger2openapi");

const swagger = YAML.parse(fs.readFileSync("swagger.yaml", "utf8"));

converter
  .convertObj(swagger, { patch: true, warnOnly: true })
  .then((options) => {
    fs.writeFileSync("openapi.yaml", YAML.stringify(options.openapi));
  });

Use a hosted converter instead of npx

SmartBear runs a hosted converter at converter.swagger.io, built on the Java swagger-parser. It has an HTTP API, so you can convert from curl without installing Node. POST the file, or pass a public URL as a query parameter. Set the Accept header to choose JSON or YAML output.

Shellterminal
# Upload a local file
curl -X POST https://converter.swagger.io/api/convert \
  -H "Content-Type: application/yaml" \
  -H "Accept: application/yaml" \
  --data-binary @swagger.yaml > openapi.yaml

# Convert a public URL
curl "https://converter.swagger.io/api/convert?url=https://petstore.swagger.io/v2/swagger.json" \
  -H "Accept: application/json" > openapi.json

Two cautions apply. The upload sends your spec to a third-party server, so do not use it for internal APIs your company has not cleared for that. Its output also differs from swagger2openapi in small ways: it targets openapi 3.0.1, adds an x-original-swagger-version extension, keeps the body parameter name as x-codegen-request-body-name, and writes content: {} on responses that have no body. The classic Swagger Editor at editor.swagger.io has an Edit menu entry, Convert to OpenAPI 3, that uses the same service. For anything you run in CI, prefer swagger2openapi so the conversion happens on your machine with a pinned version.

Where each 2.0 field ends up

Swagger 2.0OpenAPI 3.0 after conversionReview needed
host, basePath, schemesservers: one URL per schemeAdd staging or regional servers; with no host, swagger2openapi writes no servers and converter.swagger.io writes url: /
definitionscomponents/schemas, with $refs rewrittenRarely
parameters with in: bodyrequestBody with one content entry per consumes typeBody parameter name is lost unless you keep it in an extension
parameters with in: formDatarequestBody with multipart/form-data or application/x-www-form-urlencodedYes, check file fields and per-part encoding
consumes, producesMedia type keys under contentYes, every type gets a copy of the same schema
securityDefinitionscomponents/securitySchemesYes, the 2.0 types are copied as they are
Response schemacontent, one entry per produces typeSame as produces
Response examples keyed by media typeexamples or example under each media typeRename the generated keys
collectionFormatstyle and explodetsv has no 3.0 equivalent
type: filetype: string, format: binaryCheck the media type
x-nullable: truenullable: trueOther vendor extensions are left as they are
Mapping observed with swagger2openapi 7.0.8 and converter.swagger.io.

Converting a sample Pets API

The sample below has the features that cause most review work: a global produces list with two types, an Authorization header described as an apiKey, a body parameter, a multipart upload, a csv array parameter, a response example and an x-nullable field.

YAMLpets.swagger2.yaml
swagger: "2.0"
info:
  title: Pets API
  version: 1.0.0
host: api.example.com
basePath: /v1
schemes:
  - https
consumes:
  - application/json
produces:
  - application/json
  - application/xml
securityDefinitions:
  bearerAuth:
    type: apiKey
    in: header
    name: Authorization
security:
  - bearerAuth: []
paths:
  /pets:
    get:
      operationId: listPets
      parameters:
        - name: tags
          in: query
          type: array
          items:
            type: string
          collectionFormat: csv
      responses:
        "200":
          description: A list of pets
          schema:
            type: array
            items:
              $ref: "#/definitions/Pet"
          examples:
            application/json:
              - id: 1
                name: Rex
                tag: null
    post:
      operationId: createPet
      parameters:
        - in: body
          name: pet
          required: true
          schema:
            $ref: "#/definitions/Pet"
      responses:
        "201":
          description: Created
          schema:
            $ref: "#/definitions/Pet"
  /pets/{id}/photo:
    post:
      operationId: uploadPhoto
      consumes:
        - multipart/form-data
      parameters:
        - name: id
          in: path
          required: true
          type: integer
        - name: file
          in: formData
          type: file
          required: true
        - name: caption
          in: formData
          type: string
      responses:
        "204":
          description: Uploaded
definitions:
  Pet:
    type: object
    required: [id, name]
    properties:
      id:
        type: integer
        format: int64
      name:
        type: string
      tag:
        type: string
        x-nullable: true
The Swagger 2.0 source.
YAMLpets.openapi3.yaml
openapi: 3.0.0
info:
  title: Pets API
  version: 1.0.0
security:
  - bearerAuth: []
paths:
  /pets:
    get:
      operationId: listPets
      parameters:
        - name: tags
          in: query
          style: form
          explode: false
          schema:
            type: array
            items:
              type: string
      responses:
        "200":
          description: A list of pets
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Pet"
              examples:
                response:
                  value:
                    - id: 1
                      name: Rex
                      tag: null
            application/xml:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Pet"
    post:
      operationId: createPet
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
        required: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
            application/xml:
              schema:
                $ref: "#/components/schemas/Pet"
  "/pets/{id}/photo":
    post:
      operationId: uploadPhoto
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                caption:
                  type: string
              required:
                - file
        required: true
      responses:
        "204":
          description: Uploaded
servers:
  - url: https://api.example.com/v1
components:
  securitySchemes:
    bearerAuth:
      type: apiKey
      in: header
      name: Authorization
  schemas:
    Pet:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
          nullable: true
Unedited output of npx swagger2openapi pets.swagger2.yaml -o pets.openapi3.yaml (version 7.0.8). It passes validation.

The mechanical work is correct. The csv array became style: form with explode: false, which serializes the same way. The upload became a multipart/form-data requestBody with the file as type: string, format: binary, and the required list moved into the schema. x-nullable became nullable: true. Four things still need a person.

  1. bearerAuth is still type: apiKey on the Authorization header, the usual 2.0 workaround for bearer tokens. Clients generated from it will not add the Bearer prefix.
  2. Every response now lists application/xml with the same schema as JSON. If the API returns XML for real, the Pet schema probably needs xml settings; if it never did, the entry is noise.
  3. The response example became a named example called response under examples. That works, but a single example reads better as example.
  4. The body parameter name, pet, is gone. Code generators that named the method argument after it will now pick their own name.
YAMLpets.openapi3.yaml (edited, excerpt)
openapi: 3.0.3
info:
  title: Pets API
  version: 1.0.0
servers:
  - url: https://api.example.com/v1
security:
  - bearerAuth: []
paths:
  /pets:
    get:
      operationId: listPets
      parameters:
        - name: tags
          in: query
          style: form
          explode: false
          schema:
            type: array
            items:
              type: string
      responses:
        "200":
          description: A list of pets
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Pet"
              example:
                - id: 1
                  name: Rex
                  tag: null
    post:
      operationId: createPet
      x-codegen-request-body-name: pet
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Pet"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pet"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
After review: bearer scheme, JSON only, a plain example, the body name kept for codegen, and the version raised to 3.0.3. The upload path and schemas are unchanged and omitted here.

Nine fixes to make after the converter runs

Work through this list on any converted file. Each item comes from a difference between what the 2.0 file said and what you probably meant, which no converter can infer.

Review after converting Swagger 2.0 to OpenAPI 3

  • Replace apiKey bearer workarounds with an http schemeAn apiKey named Authorization in the header nearly always means a bearer token. Change it to type: http, scheme: bearer, and add bearerFormat: JWT if that is what you issue. Basic auth converts to type: http, scheme: basic on its own.
  • Trim or split media types copied from produces and consumesConverters give every listed type the same schema. Remove types the endpoint does not serve, and give XML or CSV their own schema where the body differs.
  • Check vendor extensions beyond x-nullableBoth converters turned x-nullable into nullable: true in our test. Extensions specific to a generator or gateway, such as x-omitempty or x-ms-* keys, pass through untouched. Search for x- and decide what each one should become.
  • Fix collectionFormat values with no 3.0 equivalentcsv maps to style: form, explode: false, multi to explode: true, and pipes to pipeDelimited. tsv has no 3.0 style. swagger2openapi stops with an error on it, or with --warnOnly leaves an x-collectionFormat key and an x-s2o-warning for you to resolve.
  • Review file uploads and downloadsformData file fields become format: binary properties in a multipart schema. Add an encoding entry if a part needs a specific contentType. A 2.0 response with type: file becomes a binary string under each produces type, so make sure that type is the real one, such as application/pdf.
  • Tidy examplesswagger2openapi turns response examples into named examples called response; converter.swagger.io uses example. Rename them or switch to a single example. x-example on a parameter becomes example.
  • Add discriminator mappingsA 2.0 discriminator is a property name, and the value had to equal the schema name. Converters write discriminator: { propertyName } and nothing more. If your payloads send values like cat instead of Cat, add a mapping, and consider a oneOf so clients know the allowed subtypes.
  • Restore body parameter names if codegen depends on themUse swagger2openapi -b x-codegen-request-body-name, or add the extension by hand, so generated method signatures stay stable.
  • Set servers and the version lineAdd every environment to servers, remove a bare url: / if the converter wrote one, and raise openapi: 3.0.0 to 3.0.3 to get the clarified spec text. Remove content: {} from bodyless responses.

Validate the converted file

Validate twice: once to prove the file is valid OpenAPI 3, and once to prove nothing went missing. For the first, swagger2openapi ships an oas-validate command, and Redocly CLI adds lint rules on top of structural checks. Both run without installing anything globally.

Shellterminal
# Structural validation, from the same package
npx -p swagger2openapi oas-validate openapi.yaml

# Structural checks plus lint rules
npx @redocly/cli lint openapi.yaml

oas-validate passed the converted sample. Redocly's recommended ruleset failed it, on missing operation summaries and missing 4xx responses. Those gaps were already in the 2.0 file. Conversion changes the format and leaves design problems exactly where they were, so expect lint failures on a file that never had a linter, and fix or ignore them as a separate task.

For the second check, load the old and new files side by side and compare operation counts, parameters and response codes. Paste each into the Swagger 2.0 validator and the OpenAPI validator; APIForge reads both versions, lists every operation, and lets you send a real request to confirm the server URL and the auth header still work. If the parser complains, common OpenAPI errors has fixes for the usual messages, and how to validate an OpenAPI or Swagger spec shows how to run these checks in CI so later edits stay valid. The glossary entry on OpenAPI covers the terms used here.

Frequently asked questions

For most teams, swagger2openapi. It runs locally through npx, has a Node API for build scripts, and offers --patch and --warnOnly for messy files. converter.swagger.io gives similar output without installing anything, but it uploads your spec to a hosted service.

Not cleanly with the common tools. swagger2openapi -t 3.1.0 changes the version line but leaves 3.0 keywords such as nullable. Convert to 3.0, review the output, then apply the 3.0 to 3.1 changes: type arrays for null, examples arrays and numeric exclusiveMinimum.

It can. Body parameter names disappear unless you keep them with x-codegen-request-body-name, apiKey to bearer changes alter auth handling, and new media types can add method overloads. Regenerate clients from the converted file and diff them before you merge.

No. APIForge reads Swagger 2.0, OpenAPI 3.0 and 3.1 as they are, so you can load the original and the converted file and compare their operations, but the conversion itself needs swagger2openapi or converter.swagger.io.

Sources

  1. swagger2openapi (oas-kit)GitHub
  2. Swagger ConverterGitHub
  3. OpenAPI Specification 2.0OpenAPI Initiative
  4. OpenAPI Specification 3.0.3OpenAPI Initiative
  5. Upgrading from OpenAPI 3.0 to 3.1OpenAPI Initiative