hey-api-builders is a custom Hey API plugin
that turns the official @faker-js/faker factories into immutable, type-safe
test-data builders.
Use it when a generated Faker factory is the right source of schema-aware defaults, but a builder is the more readable interface for a test:
const user = new UserBuilder().withEmail('ada@example.com').with({ role: 'admin' }).build();Version 2 has one focused job. It does not maintain a second schema interpreter, mocking runtime, or validation layer. Faker generation remains owned by Hey API; this plugin adds typed overrides, ordered transforms, lists, and builders for definitions, operation requests, and operation responses.
| Package | Supported version | Why it is needed |
|---|---|---|
| Node.js | >=22.18.0 |
Runs the generator and this plugin |
@hey-api/openapi-ts |
^0.99.0 |
Provides types, the custom-plugin API, and the Faker plugin |
@faker-js/faker |
^10.0.0 |
Used by the generated Faker factories at runtime |
| TypeScript | ^6.0.0 |
Required by Hey API at generation time |
hey-api-builders |
^2.0.0 |
Generates the builder module |
The package intentionally tracks a narrow pre-1.0 Hey API range. Hey API's custom-plugin interface is still evolving, so review both projects' release notes before upgrading outside that range.
Like Hey API itself, version 2 is an ESM package.
Install the generator and builder plugin as development dependencies, then add Faker as a runtime dependency because the generated factory module imports it:
npm install --save-dev @hey-api/openapi-ts@^0.99.0 hey-api-builders@^2.0.0 typescript@^6.0.0
npm install @faker-js/faker@^10.0.0With pnpm:
pnpm add --save-dev @hey-api/openapi-ts@^0.99.0 hey-api-builders@^2.0.0 typescript@^6.0.0
pnpm add @faker-js/faker@^10.0.0If generated Faker code is used only by tests, Faker can be a development dependency too.
Declare the TypeScript, Faker, and builder plugins in that order. Keeping the dependencies explicit makes the generated stack and its compatibility target clear:
// openapi-ts.config.ts
import { defineConfig } from '@hey-api/openapi-ts';
import builders from 'hey-api-builders';
export default defineConfig({
input: './openapi.yaml',
output: './src/client',
plugins: [
'@hey-api/typescript',
{
name: '@faker-js/faker',
compatibilityVersion: 10,
},
builders(),
],
});The default export is the canonical API. Named buildersPlugin and
defineConfig aliases are available when a named import better fits your
configuration style.
Run Hey API as usual:
npx openapi-tsThe plugin generates src/client/hey-api-builders.gen.ts. Its exports are also
included in Hey API's generated entry file by default, so either of these import
styles is available:
import { UserBuilder } from './client';
// or
import { UserBuilder } from './client/hey-api-builders.gen';The builder plugin declares the TypeScript and Faker plugins as dependencies,
but @faker-js/faker must still be installed in the consuming project. The
explicit configuration above is recommended because it also fixes Faker's
generated API to compatibility version 10.
builders({
// Shared naming case. Category-level `case` values override this.
case: 'PascalCase',
// Each category accepts a boolean, a name string/function, or this object.
definitions: {
enabled: true,
name: '{{name}}Builder',
},
requests: true,
responses: '{{name}}Fixture',
// Re-export generated symbols from the Hey API entry module.
includeInEntry: true,
});| Option | Default | Description |
|---|---|---|
case |
'PascalCase' |
Shared output casing. Supports Hey API's camelCase, PascalCase, preserve, snake_case, and SCREAMING_SNAKE_CASE values. |
definitions |
true |
Generate builders for reusable schemas. |
requests |
true |
Generate builders for aggregate operation request values. |
responses |
true |
Generate builders for operation response factories. |
includeInEntry |
true |
Re-export builder symbols from the generated entry file. It can also be a Hey API symbol predicate. |
The three category options accept these equivalent shapes:
builders({
definitions: false,
requests: '{{name}}TestBuilder',
responses: {
enabled: true,
case: 'PascalCase',
name: '{{name}}Builder',
},
});A naming string receives the category's full anchor in {{name}}. A Hey API
NameTransformer function is also accepted when a template is not sufficient.
Faker-specific options such as locale, maxCallDepth, and nameRules belong
on the @faker-js/faker plugin, not builders():
plugins: [
'@hey-api/typescript',
{
name: '@faker-js/faker',
compatibilityVersion: 10,
locale: 'en',
maxCallDepth: 4,
},
builders(),
];Builders are generated only when Hey API produced both the required TypeScript model and Faker factory. Disabling the matching TypeScript or Faker category therefore also removes its builder.
A reusable schema named User produces UserBuilder. Object definitions get a
typed convenience method for every direct property:
const user = new UserBuilder({ role: 'viewer' })
.withEmail('ada@example.com')
.withDisplayName('Ada')
.build();Property names are normalized to method-safe PascalCase suffixes:
display_name and display-name both become a method based on
withDisplayName. If normalized method names collide, the later method receives
a numeric suffix. The generated TypeScript signature remains indexed to the
original property name.
Primitive and array definitions also receive builders. Their constructor and
with() values replace the generated value as a whole rather than merging it.
An operation with ID getPet produces GetPetRequestBuilder when the official
Faker plugin emits a request factory. The value uses Hey API's aggregate request
shape, with the applicable body, headers, path, and query groups:
const request = new GetPetRequestBuilder()
.withPath({ id: 'pet-42' })
.withQuery({ 'include-history': true })
.build();Only groups present on the operation receive convenience methods. with() is
always available for a typed aggregate patch.
Response builders include the status code in their name. For example, getPet
responses for HTTP 200 and 404 produce GetPetResponse200Builder and
GetPetResponse404Builder:
const found = new GetPetResponse200Builder().with({ id: 'pet-42', name: 'Mochi' }).build();
const missing = new GetPetResponse404Builder()
.with({ code: 'not_found', message: 'No pet found' })
.build();Object response schemas also receive typed property convenience methods when their properties can be resolved from the response schema:
const found = new GetPetResponse200Builder().withName('Mochi').build();with() remains available for every response type. Responses with no status
metadata receive a unique ordinal suffix instead. A response without a Faker
factory does not produce a builder.
Hey API owns symbol conflict resolution. Reserved or colliding model names may therefore be renamed in generated output; import the emitted symbol rather than assuming an unescaped name.
Every generated class has the same immutable interface.
The optional constructor value is the initial typed patch:
const admin = new UserBuilder({ role: 'admin' });For object values, patches are shallow top-level merges. Arrays, primitive values, and nested properties are replaced. Patch values are not deep-cloned or frozen.
Returns a new builder with a shallow patch merged over the previous patch. The original builder remains usable:
const base = new UserBuilder({ role: 'viewer' });
const admin = base.with({ role: 'admin' });
base.build(); // still has role "viewer"
admin.build(); // has role "admin"Definition, request, and resolvable object-response builders expose typed
convenience methods for known properties or request groups. Each is shorthand
for with() and also returns a new builder:
const named = new UserBuilder().withDisplayName('Ada');Returns a new builder with a transform appended. Transforms run in registration
order after Faker generation and patches, on every build():
const normalized = new UserBuilder().withEmail('ADA@EXAMPLE.COM').transform((user) => ({
...user,
email: user.email.toLowerCase(),
}));The transformer is typed as (value: T) => T. It is trusted at runtime and can
still produce a value that violates the OpenAPI schema.
Calls the official generated Faker factory, applies the current patch, then applies transforms:
const user = new UserBuilder().withEmail('ada@example.com').build({
includeOptional: true,
useDefault: true,
});Builds a fresh value count times with the same patch, transforms, and factory
options:
const users = new UserBuilder({ role: 'viewer' }).buildList(10);count must be a safe, non-negative integer. 0 returns an empty array;
negative, fractional, non-finite, and unsafe integer values throw RangeError.
build() and buildList() accept the exact option type of their Hey API Faker
factory. The generated module also exports BuilderOptions<TFactory> for code
that needs to derive that first factory argument explicitly.
| Option | Faker default | Meaning |
|---|---|---|
faker |
Faker's default instance | A custom Faker instance used by the generated factory. |
includeOptional |
true |
Include optional properties. A number from 0 to 1 is the inclusion probability. |
useDefault |
false |
Prefer OpenAPI default values. A number from 0 to 1 is the probability of using a default. |
Options are forwarded unchanged. There is intentionally no builder-specific
seed option. Create and seed a Faker instance, then pass it to the build:
import { Faker, en } from '@faker-js/faker';
function createTestFaker(): Faker {
const faker = new Faker({ locale: [en] });
faker.seed(42);
faker.setDefaultRefDate('2026-01-01T00:00:00.000Z');
return faker;
}
const first = new UserBuilder().build({
faker: createTestFaker(),
includeOptional: true,
});
const second = new UserBuilder().build({
faker: createTestFaker(),
includeOptional: true,
});
// `first` and `second` are reproducible because the Faker state starts equal.Passing one seeded instance to buildList() produces a deterministic sequence.
Like Faker itself, a reused instance advances its state after every generated
value.
Builders generate data; they do not validate it. Add Hey API's zod plugin if
tests should assert that final patched or transformed values still satisfy the
schema:
plugins: [
'@hey-api/typescript',
{ name: '@faker-js/faker', compatibilityVersion: 10 },
'zod',
builders(),
];import { UserBuilder, zUser } from './client';
const user = zUser.parse(new UserBuilder().build());For network interception, pair generated data with Hey API's msw plugin or
another mocking library. Network routing is intentionally outside this
package's scope.
Version 2 is a clean rewrite, not a source-compatible extension of version 1.
- Upgrade Hey API and install Faker 10.
- Add the official
@faker-js/fakerplugin beforebuilders(). - Remove v1-only builder options.
- Regenerate the client and update imports to
hey-api-builders.gen.tsor the generated entry module. - Preserve returned builder instances when calls are not chained; v2 methods do not mutate the original builder.
Before:
builders({
generateZod: true,
mockStrategy: 'runtime',
output: 'builders.gen.ts',
});After:
plugins: [
'@hey-api/typescript',
{ name: '@faker-js/faker', compatibilityVersion: 10 },
'zod', // only when runtime validation is wanted
builders(),
];The following v1 features were removed:
mockStrategy, including the custom runtime, static, and Zod mock strategies;generateZod,useZodForMocks, anduseStaticMocks;- the custom output-name option;
- generated
setOptions()and the v1-specific mock option object; - bundled Zod generation and validation helpers;
- legacy compatibility aliases.
Equivalent schema-aware defaults now come from Hey API's official Faker plugin.
Use Hey API's official Zod plugin for validation. Runtime options now belong on
build() or buildList():
const configured = new UserBuilder().with({ role: 'admin' });
const value = configured.build({
includeOptional: true,
useDefault: true,
});- The repository's end-to-end suite exercises representative OpenAPI 2.0, 3.0, and 3.1 documents through the real Hey API generator, TypeScript compiler, Faker factories, and generated builders.
- TypeScript 7 is intentionally outside the supported range until Hey API 0.99 no longer relies on compiler APIs removed by that release.
- Schema constraint handling, optional-property probability, default selection, recursion limits, locales, and custom name rules are implemented by Hey API's Faker plugin. This package forwards its factories and options rather than reimplementing them.
- Patches are shallow. Nested objects and arrays are replaced, not deep-merged.
- Builder immutability applies to builder configuration. User-provided objects are not cloned or frozen.
- Overrides and transforms are trusted and are not runtime-validated.
- Convenience
with<Property>()methods are emitted for direct definition properties, aggregate request groups, and response properties that can be resolved from direct schemas, references, or compositions. - Builders are emitted only for definitions, requests, and responses that have the corresponding generated model/factory symbols.
- The generated output filename is owned by the plugin and is not configurable in v2.
- Webhooks, network interception, persistence, and framework fixtures are out of scope.
Use Node.js 22.18 or newer and pnpm:
pnpm install
pnpm validate
pnpm pack:checkpnpm validate checks formatting, linting, package types, end-to-end generation
and runtime behavior, coverage, and the distributable build. The end-to-end
tests invoke the public Hey API generator and compile the complete generated
tree; they do not mock the plugin API.
Bug reports should include the OpenAPI fragment, generated builder name, and
exact versions of Node.js, @hey-api/openapi-ts, @faker-js/faker, and this
package. Open an issue or pull request in the
GitHub repository.
See CHANGELOG.md for user-visible changes. Maintainer releases
use a v<package-version> GitHub release tag; publishing runs from the tagged
commit after the repository validation gates pass.