Customizing OpenAPI Generator Templates

When the SDKs that OpenAPI Generator produces are 95% right but a license header is missing, a method name reads awkwardly, or the README is generic, the answer is not to hand-patch generated code on every run. It is to override the Mustache templates the generator renders from. This guide is part of SDK Generation & Changelog Automation and shows how to extract the built-in templates, override only the files you need, and protect hand-edited output — without forking the whole template set.

How OpenAPI Generator resolves templates For each output file the generator first looks in your custom template directory, and falls back to the built-in template when no override exists. Output file api.ts Your -t directory? api.mustache present Built-in template (fallback) Rendered SDK api.ts written Override wins when present; otherwise the default renders

Problem & Context

A team generating a typescript-axios client wants every source file to carry a copyright header and the generated README.md to point at their internal registry instead of the boilerplate Markdown. The naive fixes all rot quickly:

  • Editing generated files by hand means the changes vanish the next time CI regenerates the SDK.
  • Forking the entire template directory (dozens of .mustache files per generator) means you inherit responsibility for keeping all of them in sync when you bump from one 7.x release to the next.

The generator is designed for exactly this. For every output file it renders, it first looks in the directory you pass with -t (--template-dir). If a matching .mustache file exists there, your version is used; if not, the built-in template ships the default. So the correct strategy is: override only the handful of files you actually change, and let the generator own everything else.

Step-by-Step Solution

1. Install and pin the generator

Version drift in templates is real — internal Mustache variable names and partial layouts change between major versions. Pin to a 7.x release so your overrides stay valid.

npm install -g @openapitools/openapi-generator-cli
openapi-generator-cli version-manager set 7.6.0
openapi-generator-cli version

Expected output:

Did set the version to 7.6.0
7.6.0

2. Extract the default templates for your generator

Use the author template subcommand to copy the exact built-in templates for the generator you target. This is the only reliable way to see the real variable names and partials — never guess them.

openapi-generator-cli author template \
  -g typescript-axios \
  -o openapi-templates

Expected output (truncated):

[main] INFO  o.o.codegen.TemplateManager - writing file openapi-templates/api.mustache
[main] INFO  o.o.codegen.TemplateManager - writing file openapi-templates/apiInner.mustache
[main] INFO  o.o.codegen.TemplateManager - writing file openapi-templates/model.mustache
[main] INFO  o.o.codegen.TemplateManager - writing file openapi-templates/README.mustache
...

Inspect what you got so you know which file controls what:

ls openapi-templates

3. Keep only the files you intend to override

This is the key step. Delete every extracted file except the one(s) you are changing. Here we keep only README.mustache and api.mustache:

cd openapi-templates
find . -type f ! -name 'README.mustache' ! -name 'api.mustache' -delete
ls

Expected output:

api.mustache  README.mustache

Now edit api.mustache to add a header. Mustache passes through anything that is not a {{tag}}, so a comment block goes straight to the top:

{{!-- openapi-templates/api.mustache --}}
/* Copyright (c) ACME Corp. Generated file — do not edit by hand. */
{{>licenseInfo}}
import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';

4. Generate with the template override

Point generate at the override directory with -t. The generator merges your two files over its defaults.

openapi-generator-cli generate \
  -i openapi.yaml \
  -g typescript-axios \
  -t openapi-templates \
  -o ./sdk \
  --additional-properties=npmName=@acme/api-client,supportsES6=true

Confirm the header landed:

head -n 2 ./sdk/api.ts

Expected output:

/* Copyright (c) ACME Corp. Generated file — do not edit by hand. */
/* tslint:disable */

5. Protect hand-edited files with .openapi-generator-ignore

Sometimes you want to seed a file once (a package.json with extra scripts, a custom CI file) and then never let regeneration clobber it. The .openapi-generator-ignore file lives in the output directory and behaves like .gitignore.

cat > ./sdk/.openapi-generator-ignore <<'EOF'
# Protect hand-edited files from being overwritten on regeneration
package.json
.npmignore
git_push.sh
EOF

Re-run generate and confirm those files are skipped:

[main] INFO  o.o.codegen.TemplateManager - Skipped ./sdk/package.json (Ignored by rule in .openapi-generator-ignore.)

Complete Working Example

A single script that pins the generator, extracts defaults, trims to one override, and generates — reproducible from a clean checkout.

#!/usr/bin/env bash
# build-sdk.sh — customize one OpenAPI Generator template and generate a client.
# Requires: node 18+, npx. Pins openapi-generator-cli to 7.6.0.
set -euo pipefail

GEN=typescript-axios
TPL_DIR=openapi-templates
OUT_DIR=sdk
SPEC=openapi.yaml

# 1. Pin the generator version (deterministic builds in CI).
npx --yes @openapitools/openapi-generator-cli version-manager set 7.6.0

# 2. Extract built-in templates fresh each run, then trim to the overrides we own.
rm -rf "$TPL_DIR"
npx @openapitools/openapi-generator-cli author template -g "$GEN" -o "$TPL_DIR"
find "$TPL_DIR" -type f ! -name 'api.mustache' -delete

# 3. Add a license header to the kept template (idempotent).
HEADER='/* Copyright (c) ACME Corp. Generated file — do not edit by hand. */'
if ! grep -qF "$HEADER" "$TPL_DIR/api.mustache"; then
  printf '%s\n%s' "$HEADER" "$(cat "$TPL_DIR/api.mustache")" > "$TPL_DIR/api.mustache"
fi

# 4. Generate, applying the override directory.
npx @openapitools/openapi-generator-cli generate \
  -i "$SPEC" \
  -g "$GEN" \
  -t "$TPL_DIR" \
  -o "$OUT_DIR" \
  --additional-properties=npmName=@acme/api-client,supportsES6=true,withSeparateModelsAndApi=true

# 5. Verify the override took effect; fail the build if not.
head -n 1 "$OUT_DIR/api.ts" | grep -qF "$HEADER" \
  && echo "Template override applied." \
  || { echo "ERROR: override missing"; exit 1; }

Gotchas & Edge Cases

Template file names must match exactly. The override file name has to equal the built-in template name — api.mustache, not apis.mustache or Api.mustache. If your edits do nothing, the name or the -t path is wrong. Re-extract with author template and diff your file name against the real list.

Partials are separate files. Headers and reusable blocks are often partials referenced as {{>licenseInfo}}. If you override api.mustache but the change actually lives in a partial, edit licenseInfo.mustache instead — and keep that file in your override directory too, since a partial referenced but not provided still falls back to the default.

author template is per-generator. Templates for typescript-axios are not the templates for java or python. If you generate multiple languages, keep one override directory per generator; a model.mustache from one will not render correctly under another.

Every template you override is a fork you now maintain. The generator’s own templates change between releases — bug fixes, new language features, security-relevant adjustments to generated request handling — and your copy receives none of that. A set of two or three overrides is a manageable review item on each upgrade; a set of twenty is a fork nobody has the appetite to reconcile, and the practical consequence is that the generator stops being upgraded at all.

Keep an explicit record of why each template exists, ideally as a comment at the top of the file naming the behaviour it changes and the config option that was insufficient. On each generator upgrade, diff your overrides against the upstream originals for that version and reconcile deliberately. It is unglamorous work that takes an hour and prevents the slow slide into an unupgradeable pipeline.

Prefer config to templates every time it is possible. Generators expose a long list of options precisely so that common customizations do not require touching templates, and those options are tested by the project and documented per generator. Before writing an override, read the full option list for your generator — the desired behaviour is available as a supported switch far more often than people expect, and a switch survives upgrades while a template does not.

Override count and upgrade cost A small set of template overrides is reconcilable on each generator upgrade, while a large set makes upgrading impractical. 2–3 overrides diff against upstream an hour per upgrade 8–10 overrides reconciliation is real work upgrades start slipping 20+ overrides effectively a fork the generator stops being upgraded

Snapshot the generated output in tests. Because a template change alters emitted text rather than behaviour you can assert directly, the cheapest guard is a committed sample of what the generator produces for a small fixture specification. Any template edit then shows up as a reviewable diff of real output rather than as a change whose effect nobody can see until an SDK is published.

Snapshotting generated output Generating a small fixture and committing the result makes any template change visible as a diff of real emitted code. fixture spec small, stable generate with your templates committed snapshot template edits become diffs

FAQ

How do I override only one OpenAPI Generator template file?

Extract the generator defaults with author template, keep only the single .mustache file you want to change in your template directory, and pass it back with -t. The generator falls back to its built-in templates for every file you did not provide.

Why are my template changes being ignored?

The most common cause is pointing -t at the wrong directory or naming the file incorrectly, since the override file name must match the built-in template name exactly. Run with --global-property debugModels and confirm the generator logs the path you expect.

What does the .openapi-generator-ignore file do?

It works like a .gitignore for generated output and prevents listed files from being overwritten on regeneration. Use it to protect hand-edited files such as README.md, package.json, or a custom build script.

Treat the override set as something to shrink rather than grow. Every generator release adds config options, and an override written two years ago to reach a behaviour that is now a supported switch is pure cost. Reviewing the set on each upgrade — asking of each file whether it is still necessary — is what keeps a customised generator upgradeable indefinitely.