Custom Plugin
You may need to write your own plugin if the available plugins do not suit your needs or you’re working on a proprietary use case. This can be easily achieved using the Plugin API. But don’t take our word for it – all Hey API plugins are written this way!
File Structure
Section titled “File Structure”We recommend following the design pattern of the native plugins. You can browse the code on GitHub as you follow this tutorial.
Directorymy-plugin/
- config.ts
- index.ts
- plugin.ts
- symbols.ts
- types.ts
First, create a my-plugin folder for your plugin files. Inside, create a barrel file index.ts exporting the plugin.
export { defaultConfig, defineConfig } from './config';export type { MyPlugin } from './types';index.ts references two files, so we need to create them. types.ts contains the TypeScript interface for your plugin options.
import type { DefinePlugin, Plugin } from '@hey-api/openapi-ts';
export type UserConfig = Plugin.Name<'my-plugin'> & Plugin.Hooks & Plugin.UserExports & { /** * User-configurable option for your plugin. */ user?: number | string | { age?: number; name?: string; }; };
export type Config = Plugin.Name<'my-plugin'> & Plugin.Hooks & Plugin.Exports & { /** Resolved option for your plugin. */ user: { age: number; name: string; }; };
export type MyPlugin = DefinePlugin<UserConfig, Config>;Plugin.Namesets the plugin’s unique identifier.Plugin.Hooksallows users to override plugin behavior through event hooks. For example, users can control how schemas are extracted or how symbols are routed to files.Plugin.Exportsadd theincludeInEntryoption, which controls whether your plugin’s exports are re-exported from the output entry file.UserConfigis what users write in their config file.Configis the fully resolved shape your handler receives.
Configuration
Section titled “Configuration”config.ts defines the runtime configuration for your plugin, including how user input is resolved into Config and the handler function that generates output.
import { definePluginConfig } from '@hey-api/openapi-ts';
import { handler } from './plugin';import type { MyPlugin } from './types';
export const defaultConfig: MyPlugin['Config'] = { config: { user: { age: 42, name: 'Stan Smith', }, }, handler, name: 'my-plugin',};
/** * Type helper for `my-plugin` plugin, returns {@link Plugin.Config} object */export const defineConfig = definePluginConfig(defaultConfig);The config table is a declarative resolution spec. Each field declares its default value and optional coercion rules. To allow users to set either age or name through the user field, we can add coercion rules to transform primitive values into the expected shape.
import { definePluginConfig } from '@hey-api/openapi-ts';
import { handler } from './plugin';import type { MyPlugin } from './types';
export const defaultConfig: MyPlugin['Config'] = { config: { user: { $coerce: { number: (v) => ({ age: v }), string: (v) => ({ name: v }), }, age: 42, name: 'Stan Smith', }, }, handler, name: 'my-plugin',};
/** * Type helper for `my-plugin` plugin, returns {@link Plugin.Config} object */export const defineConfig = definePluginConfig(defaultConfig);import { defineConfig } from 'path/to/my-plugin';
export default { input: 'hey-api/backend', // sign up at app.heyapi.dev output: 'src/client', plugins: [ defineConfig({ user: 'Joe Doe', // or number to set age }), ],};{ user: { age: 42, name: 'Joe Doe' }}Dependencies
Section titled “Dependencies”Plugins can declare dependencies on other plugins. A dependency will always be included in the output, even if the user hasn’t explicitly added it to their plugins config. This is useful when your plugin’s output builds on another plugin’s types or symbols.
export const defaultConfig: MyPlugin['Config'] = { config: { ... }, dependencies: ['@hey-api/typescript'], handler, name: 'my-plugin',};Resolving tags
Section titled “Resolving tags”Some dependencies aren’t known at authoring time, such as which validator plugin the user has installed. Use resolveTag inside a coerce() call to look up the active plugin for a given tag at resolution time. In most cases, you’ll also want to declare a dependency on the field to ensure it’s included in the output.
import type { PluginContext } from '@hey-api/openapi-ts';import { coerce } from '@hey-api/openapi-ts';
export const defaultConfig: MyPlugin['Config'] = { config: { $dependencies: ['validator'], validator: coerce((value, context) => { if (value === true || value === undefined) { return (context as PluginContext).resolveTag('validator'); } return value; }), }, dependencies: ['@hey-api/typescript'], handler, name: 'my-plugin',};Handler
Section titled “Handler”The handler function generates the actual output. We recommend implementing it in plugin.ts.
import { $ } from '@hey-api/openapi-ts';
import type { MyPlugin } from './types';
export const handler: MyPlugin['Handler'] = ({ plugin }) => { plugin.forEach('operation', 'schema', (event) => { if (event.type === 'operation') { // do something with the operation } else if (event.type === 'schema') { // do something with the schema } });
// use the TypeScript DSL to build nodes const symbolName = plugin.symbol('user'); const node = $.const(symbolName) .export() .assign($.literal(plugin.config.user.name)); plugin.node(node);};Symbols
Section titled “Symbols”Symbols are a type-safe way to reference identifiers your plugin works with, whether from external libraries or your own generated output. Rather than querying or constructing identifiers manually, you declare them once and access them through plugin.symbols anywhere in your handler.
Symbols are also visible to users customizing your plugin’s behavior. When a user provides hooks like ~resolvers, they receive the plugin instance and can access plugin.symbols to reference your plugin’s identifiers directly.
import type { PluginInstance } from '@hey-api/openapi-ts';
export function myPluginSymbols(plugin: PluginInstance) { return { myLib: plugin.symbol('myLib', { external: 'my-lib' }), };}
export type MyPluginSymbols = ReturnType<typeof myPluginSymbols>;import type { MyPluginSymbols } from './symbols';
/** ... */
export type MyPlugin = DefinePlugin<UserConfig, Config, never, MyPluginSymbols>;import { myPluginSymbols } from './symbols';
export const defaultConfig: MyPlugin['Config'] = { config: { ... }, handler, name: 'my-plugin', symbols: myPluginSymbols,};export const handler: MyPlugin['Handler'] = ({ plugin }) => { const { myLib } = plugin.symbols; // use myLib as a typed reference when building nodes};Once you’re satisfied with your plugin, register it in the configuration file.
import { defineConfig } from 'path/to/my-plugin';
export default { input: 'hey-api/backend', // sign up at app.heyapi.dev output: 'src/client', plugins: [ defineConfig(), ],};Output
Section titled “Output”Putting all of this together will generate the following my-plugin.gen.ts file.
export const user = 'Stan Smith';Congratulations! You’ve successfully created your own plugin! 🎉
Examples
You can view live examples on StackBlitz or on GitHub.