Skip to content

OpenAPI Generator Workflows — Automating Code Generation with GitHub Actions and CI/CD

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about OpenAPI Generator Workflows. We cover key concepts, practical examples, and best practices to help you master this topic.

OpenAPI Generator workflows automate spec-to-code pipelines in CI/CD systems, ensuring generated SDKs and server stubs are always up-to-date with the latest API specification.

What You'll Learn

  • GitHub Actions workflow for OpenAPI generation
  • GitLab CI pipeline for multi-language SDKs
  • Jenkins pipeline with spec change detection
  • Artifact publishing to package registries
  • Versioning generated code alongside specs

Why It Matters

Manual generation is error-prone and doesn't scale. Automated pipelines ensure every spec change produces tested, versioned SDKs. DodaTech's CI pipeline generates, tests, and publishes SDKs for 8 languages within 3 minutes of a spec merge, used by 200+ integration partners.

Real-World Use

A developer merges an OpenAPI spec change adding a new endpoint. The CI workflow detects the spec change, generates Python and TypeScript SDKs, runs contract tests, creates GitHub releases, and publishes to PyPI and npm — all automatically within minutes.

flowchart LR
    A["Developer pushes spec change"] --> B["GitHub Actions Triggered"]
    B --> C["Validate spec"]
    C --> D{"Spec valid?"}
    D -->|"No"| E["Fail pipeline, notify developer"]
    D -->|"Yes"| F["Generate SDKs"]
    F --> G["Python SDK"]
    F --> H["TypeScript SDK"]
    F --> I["Java SDK"]
    G --> J["Run contract tests"]
    H --> J
    I --> J
    J --> K{"All tests pass?"}
    K -->|"No"| E
    K -->|"Yes"| L["Publish to registries"]
    L --> M["PyPI, npm, Maven Central"]

Code Examples

Example 1: GitHub Actions Workflow

# .github/workflows/openapi-generate.yml
name: Generate SDKs from OpenAPI Spec

on:
  push:
    paths:
      - 'openapi/**/*.yaml'
      - 'openapi/**/*.json'
  workflow_dispatch:

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Validate OpenAPI spec
        run: |
          npm install -g @redocly/cli
          redocly lint openapi/spec.yaml

  generate:
    needs: validate
    strategy:
      matrix:
        language: [python, typescript-axios, java, go, csharp-netcore]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Java
        uses: actions/setup-java@v4
        with:
          java-version: '17'

      - name: Generate ${{ matrix.language }} SDK
        run: |
          mkdir -p generated/${{ matrix.language }}
          openapi-generator-cli generate \
            -i openapi/spec.yaml \
            -g ${{ matrix.language }} \
            -o generated/${{ matrix.language }} \
            --additional-properties=packageName=dodatech-${{ matrix.language }}

      - name: Upload ${{ matrix.language }} SDK
        uses: actions/upload-artifact@v4
        with:
          name: sdk-${{ matrix.language }}
          path: generated/${{ matrix.language }}

  test:
    needs: generate
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Download all SDKs
        uses: actions/download-artifact@v4
        with:
          pattern: sdk-*
          path: generated
      - name: Run contract tests
        run: |
          python scripts/test-sdks.py \
            --spec openapi/spec.yaml \
            --sdk-dir generated

Example 2: GitLab CI Pipeline

# .gitlab-ci.yml
stages:
  - validate
  - generate
  - test
  - publish

variables:
  SPEC_FILE: openapi/spec.yaml
  GENERATED_DIR: generated

validate-spec:
  stage: validate
  image: node:20
  script:
    - npm install -g @redocly/cli
    - redocly lint $SPEC_FILE

generate-python:
  stage: generate
  image: openapitools/openapi-generator-cli:v7.0
  script:
    - mkdir -p $GENERATED_DIR/python
    - openapi-generator-cli generate
      -i $SPEC_FILE
      -g python
      -o $GENERATED_DIR/python
      --additional-properties=packageName=dodatech_client
  artifacts:
    paths:
      - $GENERATED_DIR/python

generate-typescript:
  stage: generate
  image: openapitools/openapi-generator-cli:v7.0
  script:
    - mkdir -p $GENERATED_DIR/typescript
    - openapi-generator-cli generate
      -i $SPEC_FILE
      -g typescript-axios
      -o $GENERATED_DIR/typescript
  artifacts:
    paths:
      - $GENERATED_DIR/typescript

test-sdks:
  stage: test
  image: python:3.11
  script:
    - cd $GENERATED_DIR/python
    - pip install -e .
    - python -m pytest -v test/
    - cd ../../

publish-pypi:
  stage: publish
  image: python:3.11
  script:
    - cd $GENERATED_DIR/python
    - python setup.py sdist bdist_wheel
    - TWINE_PASSWORD=$PYPI_TOKEN twine upload --username __token__ dist/*
  only:
    - main

Example 3: Custom Python Workflow Script

import subprocess
import json
import sys
from pathlib import Path
from datetime import datetime

class OpenAPIWorkflow:
    def __init__(self, spec_path, config_path):
        self.spec_path = Path(spec_path)
        with open(config_path) as f:
            self.config = json.load(f)

    def run(self):
        """Run the complete generation workflow."""
        print(f"Starting workflow for {self.spec_path}")
        start_time = datetime.now()

        # Step 1: Validate spec
        print("\n[1/5] Validating spec...")
        self._validate_spec()

        # Step 2: Generate SDKs
        print("\n[2/5] Generating SDKs...")
        for lang_config in self.config['generators']:
            self._generate_sdk(lang_config)

        # Step 3: Generate server stubs
        print("\n[3/5] Generating server stubs...")
        for server_config in self.config.get('servers', []):
            self._generate_server(server_config)

        # Step 4: Run tests
        print("\n[4/5] Running tests...")
        self._run_tests()

        # Step 5: Package artifacts
        print("\n[5/5] Packaging artifacts...")
        self._package_artifacts()

        elapsed = (datetime.now() - start_time).total_seconds()
        print(f"\nWorkflow completed in {elapsed:.1f} seconds")

    def _validate_spec(self):
        result = subprocess.run([
            'redocly', 'lint', str(self.spec_path)
        ], capture_output=True, text=True)
        if result.returncode != 0:
            print("Spec validation failed:")
            print(result.stdout)
            sys.exit(1)
        print("Spec validation passed")

    def _generate_sdk(self, lang_config):
        lang = lang_config['language']
        output = self.config['output_dir'] / lang
        output.mkdir(parents=True, exist_ok=True)

        cmd = [
            'openapi-generator-cli', 'generate',
            '-i', str(self.spec_path),
            '-g', lang,
            '-o', str(output)
        ]
        for key, value in lang_config.get('properties', {}).items():
            cmd.extend(['--additional-properties', f"{key}={value}"])

        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode == 0:
            print(f"  {lang}: generated successfully")
        else:
            print(f"  {lang}: FAILED - {result.stderr[:200]}")

    def _run_tests(self):
        for lang_config in self.config['generators']:
            lang = lang_config['language']
            output = self.config['output_dir'] / lang
            test_dir = output / 'test'

            if test_dir.exists():
                result = subprocess.run([
                    'pytest', str(test_dir), '-v', '--tb=short'
                ], capture_output=True, text=True)
                if result.returncode == 0:
                    print(f"  {lang}: tests passed")
                else:
                    print(f"  {lang}: tests FAILED")

    def _package_artifacts(self):
        artifact_dir = self.config['output_dir'] / 'artifacts'
        artifact_dir.mkdir(exist_ok=True)
        for lang_config in self.config['generators']:
            lang = lang_config['language']
            source = self.config['output_dir'] / lang
            target = artifact_dir / f"sdk-{lang}.zip"
            subprocess.run(['zip', '-r', str(target), str(source)])

# Usage
workflow = OpenAPIWorkflow(
    'openapi/spec.yaml',
    'openapi/workflow-config.json'
)
workflow.run()

Common Mistakes

1. Generating on Every Commit

Only generate when the spec changes. Use path filters and spec hashing to avoid unnecessary runs.

2. Not Testing Generated Code

Generation is useless if the output doesn't work. Always run contract tests after generation.

3. Ignoring Generator Version Pinning

Generator CLI versions change behavior. Pin the exact version in your workflow.

4. No Spec Validation Before Generation

Generating from an invalid spec wastes time and produces broken code.

5. Publishing Unchanged SDKs

Check if the generated code actually changed before publishing. Use git diff or checksum comparison.

Practice Questions

  1. What CI events should trigger Code Generation?
  2. How do you pin the generator version in CI?
  3. Why run spec validation before generation?
  4. How do you handle spec changes that break existing clients?
  5. What artifacts should a generation workflow produce?

Answers:

  1. Pushes/merges that change spec files. Use path filters to limit triggers.
  2. Use a specific Docker image tag: openapitools/openapi-generator-cli:v7.0.0.
  3. Invalid specs produce broken code. Failing early saves time and prevents downstream issues.
  4. Use semantic versioning. Breaking changes go into a new API version. The old spec continues generating the previous SDK version.
  5. Compiled packages (wheel, npm package, JAR), documentation, test reports, and a changelog.

Challenge: Build a GitHub Actions workflow that validates an OpenAPI spec, generates Python and TypeScript SDKs, runs contract tests, and publishes to PyPI and npm only when tests pass.

FAQ

Can I generate only on spec version bumps?

: Yes. Trigger on changes to the version field in the spec, or use Git tags for versioned specs.

How do I handle spec changes that break generated code?

: Run breaking change detection (openapi-diff) before generation. Block generation if breaking changes are detected.

Can the workflow generate documentation?

: Yes. Use openapi-generator-cli with the html or html2 generator for documentation sites.

Should I commit generated code to the Repository?

: No. Generated code should be a CI artifact. Committing it causes merge conflicts and diff noise.

How do I test generated SDKs without publishing?

: Use the --dry-run flag or publish to a test package registry (TestPyPI, npm's --registry flag).

What's Next

Integrate {{< ilink "OpenAPI" "OpenAPI Generator Testing" }} into your CI workflow, and explore {{< ilink "OpenAPI" "CI/CD Code Generation with OpenAPI Generator" }} for advanced pipeline patterns.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro