# How to find which template produced a generated file

> Read the FILES manifest for what OpenAPI Generator wrote, search its extracted template set for the file's text, and prove the match with a one-line marker.

Source: https://voxgig.com/howto/find-which-template-produced-a-generated-file

- Audience: api-producer
- Level: intermediate
- Languages: typescript, java
- Verified: 2026-10-21
- Published: 2026-10-18

## Short answer

Start with `.openapi-generator/FILES` in the output directory, which lists every file the last run wrote and nothing about how. Extract the generator's own templates with `openapi-generator author template -g <name>` and search them for literal text from the file; the top hit is usually a partial, not the template whose name resembles the output. Confirm by adding one marker line to that template, regenerating with it alone in `-t`, and listing the files that carry the marker.

---
## You will need

Java 11 or later for [OpenAPI Generator](https://openapi-generator.tech/docs/usage), whose CLI is
one jar on
[Maven Central](https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.26.0/),
and Node 22 or later for the scripts. You also need an OpenAPI 3 document and the generated SDK
checked in beside it. Verified 2026-10-21 against Java 21, Node 22.22.0, and
`openapi-generator-cli` 7.26.0. The
sample's `sdk/` directory is the `typescript-fetch` output for a three-operation document, which
was generated once and committed. Every command regenerates into `build/` from the committed document,
and `npm ci` checks the jar in `vendor/` against a recorded hash, fetching it when it is missing
or does not match.

## Approaches compared

| Approach | When it fits | What it costs you | When to pick something else |
| --- | --- | --- | --- |
| [OpenAPI Generator debug global properties](https://openapi-generator.tech/docs/debugging#templates) | You want the normalized document the generator read, with every reference and default resolved | In 7.26.0 only `debugOpenAPI` prints anything; `debugModels`, `debugOperations`, and `debugSupportingFiles` print a header and no body | The question is which file a template writes, which no dump records |
| [Reading the shipped template set](https://openapi-generator.tech/docs/templating#retrieving-templates) | You need to know how a file is produced, down to the line | An extraction per generator release, forty files to search, and partials that keep the text away from the template whose name you know | You only need to know whether the generator wrote a file at all |
| [The `.openapi-generator/FILES` manifest](https://github.com/OpenAPITools/openapi-generator/blob/v7.26.0/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java) | You need to know what the last run wrote, and whether a file is generated or hand-added | A list of output paths from the last run, with no template names on it and no history of earlier runs | You already know the file is generated and need the template |

The three answer three different questions. The manifest says what was written, the debug
properties say what the generator read, and the template set says how the text came to be. The
last is the question a customization needs answered. The dumps are the cheapest to run and, in this
release, answer least, because the model dumps print a header line and stop. The template set
costs an extraction per release and a search, and it is the only one of the three that names a
template.

## Read the manifest first

Every run writes two files into `.openapi-generator` under the output directory: `VERSION`, the
generator release, and `FILES`, the path of every file the run wrote.[^1] The script reads both and
asks about one output.

```bash
node manifest.mjs models/Meter.ts
```

```text output
sdk/.openapi-generator/VERSION  7.26.0
sdk/.openapi-generator/FILES    13 files: 3 supporting, 2 api, 4 docs, 4 model
  supporting .openapi-generator-ignore
  api        apis/MetersApi.ts
  api        apis/index.ts
  docs       docs/Meter.md
  docs       docs/MeterUpdate.md
  docs       docs/MetersApi.md
  docs       docs/Problem.md
  supporting index.ts
  model      models/Meter.ts
  model      models/MeterUpdate.ts
  model      models/Problem.ts
  model      models/index.ts
  supporting runtime.ts

models/Meter.ts
  in FILES:   yes, the generator wrote it
  on disk:    yes
  template:   not recorded; the manifest names outputs, never templates
```

The manifest answers two questions. It says whether the last run wrote a path, so a file on disk
that it does not list was added by hand, and it says which release wrote it. The template is
recorded nowhere in the output, so the manifest is where a search starts and never where it ends.

## Ask the generator for its model

The [debugging documentation](https://openapi-generator.tech/docs/debugging#templates) lists four
global properties that print JSON. `debugOpenAPI` prints the document as the generator read it,
`debugModels` the data handed to model templates, `debugOperations` the data for the API
templates, and `debugSupportingFiles` the rest. The script runs all four against the sample
document twice: at the default log level, then with debug logging on.

```bash
node dump.mjs
```

```text output
openapi-generator-cli 7.26.0, generating typescript-fetch with --dry-run
  --global-property debugModels=true
    header lines "Model info": 1   JSON lines after it: 0   with -Dlog.level=debug: 0
  --global-property debugOperations=true
    header lines "Operation info": 2   JSON lines after it: 0   with -Dlog.level=debug: 0
  --global-property debugSupportingFiles=true
    header lines "Supporting file info": 1   JSON lines after it: 0   with -Dlog.level=debug: 0
  --global-property debugOpenAPI=true
    JSON lines: 178   top-level keys: openapi, info, servers, paths, components   schemas: Meter, MeterUpdate, Problem
```

Three of the four print a header and stop, whether debug logging is on or off. The cause is a
logging chain rather than a broken flag. The generator writes the header through its own logger
and hands the body to `swagger-core`, whose logger the CLI holds at `warn`.[^2] `debugOpenAPI` prints with
`System.out` and works, so the one dump you can get is the normalized document, 178 lines for
three operations with every reference and default resolved. It tells you what the generator read.
It names no template, because at that point none has run.

## Search the template set for the file's text

`author template` writes the generator's embedded templates for one target into a directory,
forty files for `typescript-fetch`. The finder cuts every mustache tag out of every template line,
keeps the literal fragments of twelve characters or more, and counts how many of each template's
fragments the generated file contains.

```js title="find-template.mjs"
export function fragments(templateText) {
  const out = new Set()
  for (const line of templateText.split('\n')) {
    for (const piece of line.split(/\{\{[^}]*\}\}+/)) {
      const text = piece.trim()
      if (text.length >= MIN) out.add(text)
    }
  }
  return [...out]
}

export function rank(generatedText) {
  return templates()
    .map((name) => {
      const all = fragments(read(join(TEMPLATES, name)))
      const hits = all.filter((f) => generatedText.includes(f))
      return { name, hits: hits.length, of: all.length }
    })
    .filter((r) => r.hits > 0)
    .sort((a, b) => b.hits - a.hits || a.name.localeCompare(b.name))
}
```

```bash
node find-template.mjs models/Meter.ts
```

```text output
models/Meter.ts: 40 templates in the typescript-fetch set, ranked by fragments the file contains
  modelGeneric.mustache             26 of  64 fragments   included by models.mustache
  modelOneOf.mustache               11 of  70 fragments   included by models.mustache
  modelEnum.mustache                 7 of  18 fragments   included by models.mustache
  apis.mustache                      5 of 147 fragments
  runtime.mustache                   5 of 329 fragments
```

The top hit is `modelGeneric.mustache`, with 26 of its 64 fragments in the file, and it is a
partial.[^3] `models.mustache`, the template the generator registers for models, is two comment
lines and four partial calls: a license header, then a choice between `modelEnum`, `modelOneOf`,
and `modelGeneric`. The second and third hits are the
other two partials it chooses between, for a `oneOf` schema and for an enum, which share lines with
the generic one. The same search on `apis/MetersApi.ts` puts `apis.mustache` first with 51
fragments, and on `docs/Meter.md` puts `model_doc.mustache` first.

## Confirm with one marker line

A ranking is a strong hint and a regeneration is proof. The probe copies one template into an
empty directory, appends a comment naming it, and regenerates with that directory as `-t`. Every
other template falls back to the embedded set, in the lookup order the
[templating documentation](https://openapi-generator.tech/docs/templating#retrieving-templates)
gives: your directory first, the embedded files after.

```js title="confirm.mjs"
export function probe(templateName) {
  templates()
  const dir = `build/probe/${templateName.replace(/\.mustache$/, '')}`
  rmSync(dir, { recursive: true, force: true })
  mkdirSync(join(dir, 'templates'), { recursive: true })
  const marker = `// probe: ${templateName}`
  writeFileSync(join(dir, 'templates', templateName), read(join(TEMPLATES, templateName)) + `\n${marker}\n`)
  generate(join(dir, 'out'), ['-t', join(dir, 'templates')])
  return tree(join(dir, 'out')).filter((f) => read(join(dir, 'out', f)).includes(marker))
}
```

```bash
node confirm.mjs modelGeneric.mustache
```

```text output
modelGeneric.mustache with one marker line, regenerated with only that file in -t
  files carrying the marker: 3
    models/Meter.ts
    models/MeterUpdate.ts
    models/Problem.ts
```

The marker reaches the three model files and no other output, which is the answer the manifest and
the dumps cannot give: this template, these outputs, in this release. The probe directory holds one
file, so the regeneration is otherwise the stock one, and the diff against the committed client is
three comment lines.

## Check it worked

```bash
node --test find.test.mjs
```

```text output
1..9
# tests 9
# suites 0
# pass 9
# fail 0
# cancelled 0
# skipped 0
# todo 0
```

Nine tests. `VERSION` equals what the jar reports, and `FILES` equals the tree on disk with the
metadata left out. A regeneration from the committed document reproduces every committed file byte
for byte, which is the check that makes the committed client trustworthy at all. The ranking puts
the right template first for a model, an API, the runtime, and a documentation page.
`modelGeneric` is included by `models.mustache` alone, and the two probes land where the outputs
say. The last two keep the inputs pinned: a jar on disk that fails the recorded hash is fetched
again, and a template set that another jar extracted is extracted again.

## When it goes wrong

```bash
node pitfall.mjs
```

```text output
models/index.ts: the template the name suggests, then the one the text is in
  index.mustache             marker lands in: index.ts
  models.index.mustache      marker lands in: models/index.ts

models/Meter.ts: no template is called Meter, and the one called models holds two literal lines
  templates with "meter" in the name: none
  top template by text:               modelGeneric.mustache, 26 fragments
  literal lines in models.mustache:   2: /* tslint:disable */ | /* eslint-disable */
```

The name misleads. `models/index.ts` looks like the work of `index.mustache`, and a marker there
lands in the top-level `index.ts` only; `models.index.mustache` is the template, with a dot where
the directory separator is. For `models/Meter.ts` there is no template with the model's name,
because one template writes every model, and the template that carries the directory's name holds
two literal lines.

The dump is empty. `--global-property debugModels=true` prints the header and nothing after it,
and `-Dlog.level=debug` changes nothing, because it raises the generator's own logger and leaves
the one the body goes through at `warn`. Use `debugOpenAPI` for the document and the template set
for the model.

The templates are from the wrong release. A set extracted from a newer jar than the one in
`VERSION` matches lines that have since moved, and a probe against it regenerates a different
client. Extract from the jar the manifest names, which is what the first test pins.
`generator.mjs` stamps the set with the SHA-256 of the jar that extracted it and extracts again
when the jar changes, which the last test pins.

## When not to do this

Do not copy the whole template set to change one line. The probe here works because its directory
holds one file and every other template falls back to the embedded set, and a customization built
the same way stays one reviewable file. A full copy freezes forty files against every later release
and hides the one you changed.

Do not edit the generated file instead of finding its template. The edit survives until the next
run, and the run that removes it is the one somebody else starts.

Do not read the manifest as a list of what is safe to change. It says what the generator wrote,
not what it owns. A file it lists is rewritten on the next run whether or not you edited it, and
the ignore file is the only thing that changes that.

A template set from another release is the wrong one to search, and an extracted set does not
belong in the repository. Extract from the jar `VERSION` names, into a build directory, each time
you need it.

## Related how-tos

- [Preview a regeneration as a diff before writing files](/howto/preview-a-regeneration-as-a-diff)

- [Fail a build when a generator customization becomes a fork](/howto/audit-generator-customisations-for-drift)

- [Generate a code section with code instead of a template](/howto/generate-a-code-section-with-a-component)

## Last verified

Verified 2026-10-21 against Java 21, Node 22.22.0, and `openapi-generator-cli` 7.26.0, fetched
from Maven Central by `npm ci`. Every output block is what the command preceding it printed. The
logging chain behind the empty dumps was read from the generator and `swagger-core` sources at the
tags the notes name, not stepped through in a debugger.

[^1]: The manifest's path separator is settled in a source comment. The
[generator](https://github.com/OpenAPITools/openapi-generator/blob/v7.26.0/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java)
writes Linux-style paths to `FILES` on purpose, the comment explaining that `File.separator`
would produce paths that fail to match on Windows. A second comment beside it guards against a
null path seen once on CI, to be fixed, it says, in the 5.0 template management refactor. The
code shipping in 7.26.0 still carries the note.

[^2]: The chain has three links in two repositories. In 7.26.0 the
[generator](https://github.com/OpenAPITools/openapi-generator/blob/v7.26.0/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java)
logs the header with its own logger at `info` and calls `Json.prettyPrint` for the body. That
method, in [`swagger-core` 2.2.52](https://github.com/swagger-api/swagger-core/blob/v2.2.52/modules/swagger-core/src/main/java/io/swagger/v3/core/util/Json.java),
serializes the object and hands the string to a helper that logs it at `debug` on the `Json`
class's own logger. The CLI's
[`logback.xml`](https://github.com/OpenAPITools/openapi-generator/blob/v7.26.0/modules/openapi-generator-cli/src/main/resources/logback.xml)
sets `io.swagger` to `warn`, `org.openapitools` to the `log.level` property with `info` as the
default, and the root to `error`. So `-Dlog.level=debug` opens the generator's logger and leaves
the one carrying the body closed, which is why the header prints with nothing under it.

[^3]: The [mustache(5) manual](https://mustache.github.io/mustache.5.html) says partials are
rendered at runtime rather than compile time, so recursive partials are possible, and advises
avoiding infinite loops. It then suggests thinking of partials as includes, or template
expansion, and adds in the same sentence that this is not literally true. A template whose text
lives in four other files is the ordinary case the manual is describing, with the include
picture offered and withdrawn in one breath.