# ts-auto-guard: generating type guards from the interfaces you already wrote

> ts-auto-guard reads your TypeScript interfaces and writes the runtime checks that prove an unknown value is one of them, so the code that parses a JSON file or an API response can be verified rather than asserted. The README is blunt about its own state, warning that the project is largely unmaintained and has a few issues, and pointing at typia as a superior alternative, which is the first thing to weigh before adopting it.

**rhys-vdw/ts-auto-guard** — Generate type guard functions from TypeScript interfaces

- Repository: https://github.com/rhys-vdw/ts-auto-guard
- Stars: 525 · Forks: 52
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/rhys-vdw-ts-auto-guard

## What it does, and the warning to read first

ts-auto-guard is a tool for automatically generating TypeScript type guards for interfaces in your code base, and the aim is to let developers verify data from untyped sources to ensure it conforms to TypeScript types, for example when initializing a data store or receiving structured data in an AJAX response.

So the problem it addresses is the one every TypeScript project has at its edges. The compiler trusts your types, but the data arriving from a file, a database or a network response is unknown at runtime, and the only way to find out is to check. Writing those checks by hand is tedious and drifts out of date, and the alternative is an unchecked cast that moves the problem to the first time the data is wrong.

The warning comes second in the README and it should come first in your reading. This project is largely unmaintained and has a few issues, and the recommendation is to try typia, which looks superior. The last push was on 2026-04-28 and the recent releases are v5.1.3, v5.1.2 and v5.1.1, all on that same day, which is the shape of a burst of releases followed by a long pause. That is consistent with a maintainer tidying things up rather than a project with an active queue.

The primary language is TypeScript, the licence is MIT, the default branch is master, and the package is published on npm as ts-auto-guard. The author is Rhys van der Waerden, and the package version in the manifest is 5.1.3.

The honest reading is that this is a useful tool with an alternative that may serve you better, and the documentation is unusually good about the features it does have. If you evaluate it, evaluate it against typia rather than against nothing.

## Installing, and why strict mode matters here

Installation is a dev dependency, given in two flavours:

```bash
yarn add -D ts-auto-guard
```

or

```bash
npm install --save-dev ts-auto-guard
```

It is a development dependency because the generated files are committed to your repository, not because the guards are generated at build time. That is the central design decision of the tool, and it has consequences you should think about before choosing it.

The generated guard file sits next to the interface it was made from, in the example my-project/Person.guard.ts next to my-project/Person.ts. The file is checked in, the tool is run by hand or by a script, and the output is reviewed like any other source. The upside is that the guards work at runtime with no build step and no dependency in production, and the downside is that regenerating them is a manual action that can be forgotten.

The TypeScript configuration section says it makes sense to use this library in strict mode, and to turn on the strict mode family options by defining strict true in tsconfig.json under compilerOptions. And it adds a warning that if you have problems you should check that the strict mode family options, such as strictNullChecks, are not explicitly set to false, with two issue links for more information.

That warning is a real gotcha rather than boilerplate. The tool's output depends on how the compiler treats optional properties, and a project that has strict turned on but strictNullChecks explicitly disabled is in a state where the generated guards and the type declarations do not agree.

Then the usage, which is a single command:

```bash
ts-auto-guard ./my-project/Person.ts
```

You specify which types to process and run the CLI in the same folder as your project's tsconfig.json, optionally passing in the paths of the files you would like it to parse. The tool needs the tsconfig because it is reading the type system, not parsing text.

## What the generated guard looks like

The generated file is a separate module that imports the type and exports a predicate function:

```ts
// my-project/Person.guard.ts

import { Person } from './Person'

export function isPerson(obj: unknown): obj is Person {
  const typedObj = obj as Person
  return (
    typeof typedObj === 'object' &&
    typeof typedObj['name'] === 'string' &&
    (typeof typedObj['age'] === 'undefined' ||
      typeof typedObj['age'] === 'number') &&
    Array.isArray(typedObj['children']) &&
    typedObj['children'].every(e => isPerson(e))
  )
}
```

So the shape is a type predicate function, isPerson taking an unknown and returning obj is Person. The body casts once to Person and then checks each property with typeof, treating the optional age as either undefined or a number, and recursing into the children array with an every call that invokes isPerson on each element.

That last part is the interesting one. The interface has children typed as Person[], so the guard has to check that the array exists and that every element is a Person, and it does so by calling the same generated function. A recursive type produces a recursive guard, which is the part that is tedious to write by hand and the part that makes the tool worth having.

The use in your project is then the ordinary pattern for narrowing an unknown:

```ts
// index.ts

import { Person } from './Person'
import { isPerson } from './Person.guard'

// Loading up an (untyped) JSON file
const person = require('./person.json')

if (isPerson(person)) {
  // Can trust the type system here because the object has been verified.
  console.log(`${person.name} has ${person.children.length} child(ren)`)
} else {
  console.error('Invalid person.json')
}
```

You load an untyped JSON file with require, check it with the guard, and inside the true branch the type system can be trusted, so person.name and person.children.length are typed. In the else branch you log that the file is invalid.

The important detail is the comment in the true branch: can trust the type system here because the object has been verified. That is the whole value proposition. The check narrows the type, so the code after it is type-safe without a single cast, and the cast inside the generated guard is the one place a cast is acceptable because it is immediately followed by the verification that makes it safe.

The generated code is deliberately plain. It is typeof checks and array methods, not a schema library, so it depends on nothing at runtime and the output is readable enough to review by eye when you regenerate it.

## Selecting which types to process

There are two ways to choose what gets a guard, and the annotated one is the default.

You annotate an interface in your project with a JSDoc see tag, and the tool generates guards only for interfaces carrying it:

```ts
// my-project/Person.ts

/** @see {isPerson} ts-auto-guard:type-guard */
export interface Person {
  // !do not forget to export - only exported types are processed
  name: string
  age?: number
  children: Person[]
}
```

The tag form is @see {name} ts-auto-guard:type-guard, and the JSDoc link tag is also supported in the form @see {@link name} ts-auto-guard:type-guard. The name in braces is the name of the generated function, so you choose it rather than accepting a default.

The comment inside the interface is the part worth pausing on: do not forget to export, only exported types are processed. That is a real constraint on where you can put the annotation, since an interface you keep internal to a module will be silently skipped, and a tool that silently skips is worse than one that errors.

The alternative is to process everything:

```bash
ts-auto-guard --export-all 'src/domain/*.ts'
```

The --export-all parameter processes all exported types, and the path pattern is quoted so the shell does not expand it before the tool sees it.

The trade is between the two. Annotation means the generated files appear in review as deliberate additions next to a deliberate marker, which is nice for a codebase where only a few types are untrusted at the boundary. The export-all mode is less work to set up and produces guards for a lot of types that do not need them, which is noise in review and more code in the repository.

For most codebases the annotation approach is the right one, because the untrusted boundary is usually a small number of places, and a generated file appearing next to a type you did not expect is a signal that the selection is too broad.

## The flags that change the generated code

There are five flags that alter what is written, and each of them exists for a concrete reason.

Debug mode changes the output type guards to log the path, expected type and value of failing guards, which is for working out why your type guards are failing in development. The example is:

```bash
ts-auto-guard --debug
```

So a failing guard tells you not just that it failed but which field and what it got. With a nested type, that is the difference between a debugging session and a guess.

The shortcircuit flag causes all guards to always return true, and it takes an expression:

```bash
ts-auto-guard --shortcircuit="process.env.NODE_ENV === 'production'"
```

The generated code then tests the expression first and returns true, before the real checks. The README explains the intent: used in combination with uglify-js's dead_code and global_defs options, this lets you omit the long and complicated checks from your production code, so the checks exist in development and the bundler removes them from the build that ships. That is a real technique and the reason the option exists is that the generated checks are not cheap.

The guard file name defaults to .guard.ts and can be overridden, so ts-auto-guard --guard-file-name="debug" results in a guard file called .debug.ts. That is the mechanism for having a development variant and a production variant of the same file.

The import-guards flag adds an import statement at the top and a named export at the bottom of the source files for the generated guards, and it optionally accepts a custom name for the import alias, with TypeGuards as the default when none is passed. So ts-auto-guard --import-guards="Guards" results in an import of the guard module as Guards and a matching export at the bottom. The prevent-export-imported flag overrides that behaviour and does not have the guards exported from the source.

And the import-extension flag sets a custom extension in the generated import statements. The default is no extension, and the README is explicit about why that matters: it does not work with ESM, which requires the .js extension for import statements. So ts-auto-guard --import-extension="js" produces an import of the type from the .js path, and the flag name is the fix for a common and confusing breakage.

## Custom guards, and the limitations the README admits

The last section covers the case where the tool should not generate a guard at all.

ts-auto-guard cannot generate type-guards for every possible type, and even when it can, sometimes it is better to use a pre-existing validator. You annotate the type with @see {name} ts-auto-guard:custom, where name is a function exported from the current file, and the tool delegates to that function instead of generating one.

The first of the three listed use cases is branded and nominal types. Branded and nominal types carry a marker that is only visible to the type system and absent at runtime, so there is no way for the tool to generate a structural check to validate them. The branded type is a compile-time fiction, and a runtime check for it is impossible, so the only honest option is to delegate to something you wrote.

This is the clearest illustration of what the tool can and cannot do, and it generalises. A type guard is a runtime function, so it can only check what exists at runtime. Branded types, and any other compile-time-only construct, are outside what a runtime check can express.

The other two use cases are cut off in the available documentation, but the pattern is clear from the first one: the tool has a designated escape hatch for types it cannot handle, and using it is a supported path rather than a workaround.

The other limitations are stated plainly elsewhere in the README. Only exported types are processed, which means an internal interface is skipped. The strict mode family has to be on, and a project with strictNullChecks explicitly set to false will produce guards that disagree with the types. And the tool cannot generate a check for a type whose distinguishing feature exists only in the type system.

Those are the real constraints, and they are all stated rather than discovered.

## How to evaluate it, and what to check first

There are five things to consider, and the first one is the warning at the top of the README.

The project is largely unmaintained and has a few issues, and the author recommends typia as a superior alternative. The last release was on 2026-04-28 and the three most recent are all from the same day, which reads as a cleanup rather than a cadence. If you are starting a new integration, evaluate typia first and come back only if it does not fit. If you already depend on this package, the situation is different, because a working tool that is not maintained is still better than a migration during an incident.

Second, the commit-or-generate model. The generated files are checked into your repository, so the tool needs to be run and the output reviewed. That is a feature if you want the checks to be reviewable and to work at runtime with no dependency, and a chore if your types change often, because every change to an interface means regenerating. The --import-guards flag reduces the churn by wiring the imports into the source files, but it also means the tool writes to your source.

Third, the strict mode requirement. A project with strict on but strictNullChecks explicitly false will produce guards that do not match the types, and the README links two issues about it. Check your tsconfig before you generate anything.

Fourth, the types you cannot guard. Branded and nominal types have to be delegated to a function you write, and the delegation annotation is the mechanism. If your domain model leans on branded types, the tool covers less of it than you might expect.

Fifth, the runtime cost and how you manage it. The generated checks are a chain of typeof calls and an every over arrays, and for a large payload that is not free. The shortcircuit flag exists to remove them in production, and it is worth reading about that combination with uglify-js before you decide whether you need it.

What you get is a well-documented tool that does a tedious job, with a debug mode that makes failures diagnosable, and a set of flags for the cases where you want the checks off in production. The cost is that it is not maintained, the alternative is better, and the generated code is code you now own.

## Conclusion

Adopt ts-auto-guard if you already depend on it, or if you want generated runtime checks for the untrusted edges of an existing codebase and have looked at typia first and found it does not fit. Do not start a new integration without reading the warning at the top of the README, because the project is largely unmaintained and its own author points elsewhere. Before you commit, check that the strict mode family is genuinely on in your tsconfig rather than strict with strictNullChecks set to false, because the generated guards will not match the types otherwise, and decide how you will handle regeneration, since the generated files are committed and every change to an interface means running the tool again.

## FAQ

### What is ts-auto-guard?

It is a tool for automatically generating TypeScript type guards for interfaces in your code base, so that data from untyped sources can be verified to conform to TypeScript types, for example when initializing a data store or receiving structured data in an AJAX response. It is MIT licensed and published on npm as ts-auto-guard.

### Is ts-auto-guard still maintained?

The README says no. It carries a warning that the project is largely unmaintained and has a few issues, and recommends trying typia, which it says looks superior. The three most recent releases, v5.1.1, v5.1.2 and v5.1.3, were all published on 2026-04-28, and the last push to the repository was on the same date.

### How do I tell ts-auto-guard which types to process?

Annotate the interface with a JSDoc see tag of the form @see {name} ts-auto-guard:type-guard, where the name in braces becomes the generated function name, and the JSDoc link tag form is also supported. Alternatively, the --export-all parameter processes all exported types. Only exported types are processed in either case.

### What is the shortcircuit flag for?

It causes all generated guards to always return true, and it takes an expression so the behaviour can be conditional. Used with uglify-js's dead_code and global_defs options, it lets the long and complicated checks be omitted from production code, so the checks exist during development and the bundler removes them from the shipped build.

### How does ts-auto-guard handle types it cannot generate a guard for?

You annotate the type with @see {name} ts-auto-guard:custom, where name is a function exported from the current file, and the tool delegates to it instead of generating a guard. The first listed use case is branded and nominal types, which carry a marker visible only to the type system and so cannot be checked structurally at runtime.

## Sources

- [Issues](https://github.com/rhys-vdw/ts-auto-guard/issues)
- [License: MIT](https://github.com/rhys-vdw/ts-auto-guard/blob/master/LICENSE)
- [README](https://github.com/rhys-vdw/ts-auto-guard/blob/master/README.md)
- [Releases](https://github.com/rhys-vdw/ts-auto-guard/releases)
- [rhys-vdw/ts-auto-guard on GitHub](https://github.com/rhys-vdw/ts-auto-guard)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rhys-vdw-ts-auto-guard
