Naming
How schema names and operations in a specification become TypeScript identifiers, and the options that shape them: prefix, suffix, stripSchemaPrefix, stripPathPrefix and useOperationId. Read it when a generated name is not what you expected, when two specifications must coexist in one project, or before you rely on a name in test code.
All four generators derive names with the same rules, so a type in the TypeScript output, a schema in the zod output and a method in the Playwright or k6 client agree with each other. The differences are in casing and in what each generator appends, and they are shown per generator below.
From a schema name to an identifier
A schema name is first normalised into a valid identifier. Dots, hyphens, underscores and spaces are word separators; the words are capitalised and joined; any other character that cannot appear in an identifier becomes a separator too; a name that would start with a digit is prefixed with N.
| Schema name | Normalised identifier |
|---|---|
User | User |
Company.Models.User | CompanyModelsUser |
item-create-request | ItemCreateRequest |
Org_Entity_POST | OrgEntityPOST |
User@Domain | UserDomain |
2faSettings | N2faSettings |
Because the normalisation collapses separators and case, two schemas can normalise to one identifier (pet-kind and Pet_Kind); that is reported as a name collision, described in specs-and-references.md.
Each generator then emits the identifier in its own form:
| Generator | Schema Company.Models.User becomes |
|---|---|
openapi-to-typescript, openapi-to-k6 | export type CompanyModelsUser = { ... } |
openapi-to-zod, openapi-to-zod-playwright | export const companyModelsUserSchema = z.strictObject({ ... }) and export type CompanyModelsUser = z.infer<typeof companyModelsUserSchema> |
The zod schema constant is camelCase with the suffix Schema; the inferred type keeps the PascalCase name. An enum schema follows the same pattern (companyModelsUserRoleSchema, CompanyModelsUserRole).
stripSchemaPrefix
Specifications generated from namespaced code carry the namespace in every schema name. stripSchemaPrefix removes it before the name is normalised, so references inside the generated file become short as well:
components:
schemas:
Company.Models.User:
type: object
required: [id, name]
properties:
id: { type: string }
name: { type: string }
role: { $ref: "#/components/schemas/Company.Models.UserRole" }
Company.Models.UserRole:
type: string
enum: [admin, member]
export default defineConfig({
specs: [{ input: "openapi.yaml", outputTypes: "src/types.ts", stripSchemaPrefix: "Company.Models." }],
});
export const UserRole = {
Admin: "admin",
Member: "member",
} as const;
export type UserRole = (typeof UserRole)[keyof typeof UserRole];
export type User = {
id: string;
name: string;
role?: UserRole;
};
The value is a literal prefix or a glob pattern, or an array of either; with an array, the first pattern that matches is applied. A literal is removed wherever the name starts with it, including mid-word (stripSchemaPrefix: "Company" turns CompanyUser into User), because schema names are not paths. A glob strips the longest leading part of the name that matches it:
| Pattern | Strips |
|---|---|
"Company.Models." | exactly that text |
"*.Models." | Company.Models., App.Models. (one segment before) |
"**.Models." | Company.Api.Models. (any number of segments) |
"Company.{Models,Services}." | either namespace |
"api_v[0-9]_" | api_v1_, api_v2_ |
A name that does not match is left alone, and a malformed glob is reported with Invalid glob pattern "..." and ignored.
prefix and suffix
prefix and suffix are added to every schema-derived name after any stripping, so the three options compose as strip, then prefix, then suffix. With stripSchemaPrefix: "Company.Models.", prefix: "Api" and suffix: "Dto", the TypeScript generator writes:
export type ApiUserDto = {
id: string;
name: string;
role?: ApiUserRoleDto;
};
The zod generator applies both to the schema constant and leaves the inferred type name alone, so with prefix: "api" and suffix: "dto":
export const apiUserDtoSchema = z.strictObject({
id: z.string(),
name: z.string(),
role: apiUserRoleDtoSchema.optional(),
});
export type User = z.infer<typeof apiUserDtoSchema>;
This is the combined output, where schema and type share one file. When outputZodSchemas splits them, the types file is written by the TypeScript generator and its type names do carry the affixes (ApiUserDto), like any TypeScript output. The casing of the affixes is adjusted to the name they join (api becomes Api in a PascalCase type, Dto keeps its capital in a camelCase constant), so you can use the same values for every generator. The usual reason to set them is two specifications in one project whose schemas share names, or a house convention such as Dto. They also separate two schemas that normalise to the same identifier when you cannot rename them in the specification.
From an operation to a name
Operations have no name of their own, so one is derived. useOperationId decides from what:
useOperationId | Name comes from |
|---|---|
true | The operation's operationId, normalised like a schema name. Operations without one fall back to the path. |
false | The HTTP method and the path, always. |
The default differs: the TypeScript and zod generators default to true, the Playwright and k6 generators to false. A client whose methods are named after the URL is predictable without opening the specification, which is why the two generators that emit clients default that way; the two that emit only types and schemas prefer the specification's own vocabulary. Set the option explicitly when the two outputs must line up.
A path-derived name is the method followed by each path segment, capitalised and joined, with a parameter segment {name} becoming By and the parameter name:
| Operation | Operation name | Playwright or k6 method |
|---|---|---|
GET /users | GetUsers | getUsers |
GET /api/v1/users/{user-id} | GetApiV1UsersByUserId | getApiV1UsersByUserId |
POST /auth/login | PostAuthLogin | postAuthLogin |
GET /items/{a},{b} | GetItemsByAB | getItemsByAB |
An operationId is normalised the same way a schema name is, and lower-cased at the front for a method: list-users gives ListUsers as an operation name and listUsers as a method; createUser gives CreateUser and createUser.
The operation name is the stem of every operation-derived identifier. The TypeScript and zod generators emit these for an operation named ListUsers:
| Identifier | When |
|---|---|
ListUsersQueryParams, listUsersQueryParamsSchema | The operation has query parameters |
ListUsersHeaderParams, listUsersHeaderParamsSchema | It has header parameters not removed by ignoreHeaders |
ListUsersRequest | Its request body is declared inline rather than by $ref |
ListUsersJsonRequest, ListUsersFormRequest | As above, with several content types; the suffix names the media type |
ListUsersResponse | A response is declared inline and is the operation's only success response |
ListUsersCreatedResponse, ListUsersNotFoundResponse | The response needs a status suffix (see below) |
prefix and suffix wrap the parameter types (ApiListUsersQueryParamsDto) but not the inline request and response types.
stripPathPrefix
An API mounted under a version prefix repeats it in every path, and so in every path-derived name. stripPathPrefix removes it before names are derived:
export default defineConfig({
specs: [
{
input: "openapi.yaml",
outputTypes: "tests/schemas.ts",
outputClient: "tests/client.ts",
stripPathPrefix: "/api/v1",
basePath: "/api/v1",
},
],
});
With it, GET /api/v1/users/{user-id} is named getUsersByUserId instead of getApiV1UsersByUserId, and its query parameter type GetUsersQueryParams instead of GetApiV1UsersQueryParams. The JSDoc of the generated service shows the stripped path. Stripping changes names only: the client still has to call the real URL, which is what basePath is for in the Playwright and k6 generators; it is prepended to every request. The option is accepted by all four generators and affects whichever path-derived names each one emits.
A literal prefix is normalised before matching: a missing leading slash is added and a trailing slash removed, so "api/v1", "/api/v1" and "/api/v1/" are the same prefix. It has to end at a segment boundary: "/api" strips /api/users to /users but leaves /apikeys/{id} alone. A glob pattern matches the longest leading part of the path it can, and the remainder always starts with /:
| Pattern | Matches |
|---|---|
"/api/v*" | /api/v1, /api/v2, /api/v10 |
"/api/v*.*" | /api/v1.0, /api/v2.5 |
"/api/v[0-9]" | /api/v1 to /api/v9 |
"/api/{v1,v2}" | /api/v1 or /api/v2 |
"/api/**/v1" | /api/v1, /api/internal/v1 |
In the Playwright and k6 generators the includePaths and excludePaths filters see the stripped path; in the TypeScript and zod generators they see the original (see operation-filters.md).
Method names in the Playwright client and service
The Playwright client has one method per operation, named as described above. The service has one method per generated response of an operation, and when there is more than one the status code has to be part of the name. The suffix is the reason phrase of the status, in PascalCase, never the number:
| Status | Suffix | Status | Suffix |
|---|---|---|---|
200 | Ok | 400 | BadRequest |
201 | Created | 401 | Unauthorized |
202 | Accepted | 403 | Forbidden |
204 | NoContent | 404 | NotFound |
409 | Conflict | 422 | UnprocessableContent |
500 | InternalServerError | 503 | ServiceUnavailable |
When a suffix is needed, a code without a standard phrase keeps its digits (postUsers299), a range key such as 2XX keeps its text, and OpenAPI's default response becomes Default. The suffix is added only when it disambiguates:
- A response that is the operation's only success response gets no suffix.
GET /userswith a single200isgetUsers(), returningGetUsersResponse. - When an operation declares more than one success response, every success response is suffixed:
postUsersCreated(),postUsersAccepted(). - A non-success response is always suffixed, whatever else the operation declares:
getUsersByUserIdNotFound(),postUsersBadRequest(). Those methods exist only whenincludeStatusCodesasks for them.
Whether an operation "declares more than one success response" is read from the specification before any status-code filter is applied, so filtering responses never renames the ones that remain. The inline response schemas and types use the same suffix (postUsersCreatedResponseSchema, PostUsersCreatedResponse), which is what keeps a method aligned with the type it returns. For the specification used throughout this page, the service generated with includeStatusCodes: ["2xx", "4xx"] and stripPathPrefix: "/api/v1" is:
async getUsers(options?: { params?: GetUsersQueryParams }): Promise<GetUsersResponse>
async postUsersCreated(options: { data: User }): Promise<User>
async postUsersAccepted(options: { data: User }): Promise<void>
async postUsersBadRequest(options: { data: User }): Promise<Problem>
async getUsersByUserId(userId: string): Promise<User>
async getUsersByUserIdNotFound(userId: string): Promise<Problem>
and, with useOperationId: true, listUsers, createUserCreated, createUserAccepted, createUserBadRequest, getUser and getUserNotFound. Path parameters become camelCase arguments (user-id is userId), and a parameter name that starts with a digit is prefixed with _.
The class names come from the output file names: tests/client.ts and tests/service.ts give ApiClient and ApiService, while tests/orders-client.ts gives OrdersClient and the matching service OrdersService. The k6 generator names its classes Client and Service unless the file name adds a stem in the same way.
The name of a specification entry
Every entry in specs may carry a name, a label of your own:
{ name: "orders-v2", input: "specs/orders-v2.yaml", outputTypes: "src/orders-v2.ts" }
It is accepted by every generator and cannot be set in defaults. It does not change any generated identifier, and the batch output currently reports entries by their input path rather than by this label, so for now it serves to document a configuration with several entries. Nothing else on this page depends on it.
Where the options are listed
Every option named here is in each generator's reference page, with its type and default: options-typescript.md, options-zod.md, options-zod-playwright.md, options-k6.md.