Custom Plugin
Plugin API is in development. The interface might change before it becomes stable. We encourage you to leave feedback on GitHub.
You may need to write your own plugin if the available plugins do not suit your needs or youre working on a proprietary use case. This can be easily achieved using the Plugin API. But dont take our word for it all Hey API plugins are written this way!
File Structure
Section titled File StructureWe 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
- imports.ts
- index.ts
- plugin.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';Types
Section titled Typesindex.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 plugins 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 plugins 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 Configurationconfig.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 DependenciesPlugins can declare dependencies on other plugins. A dependency will always be included in the output, even if the user hasnt explicitly added it to their plugins config. This is useful when your plugins output builds on another plugins types or symbols.
export const defaultConfig: MyPlugin['Config'] = { config: { ... }, dependencies: ['@hey-api/typescript'], handler, name: 'my-plugin',};Resolving tags
Section titled Resolving tagsSome dependencies arent 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, youll also want to declare a dependency on the field to ensure its 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 HandlerThe 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);};Imports
Section titled ImportsImports are a type-safe way to reference external symbols your plugin works with. Rather than querying or constructing identifiers manually, you declare them once and access them through plugin.imports anywhere in your handler.
Imports are also visible to users customizing your plugins behavior. When a user provides hooks like $resolvers, they receive the plugin instance and can access plugin.imports to reference your plugins imports directly.
import type { PluginInstance } from '@hey-api/openapi-ts';
export function myPluginImports(plugin: PluginInstance) { const factory = plugin.symbolFactory; return { myLib: factory.register('myLib', { external: 'my-lib' }), };}
export type MyPluginImports = ReturnType<typeof myPluginImports>;import type { MyPluginImports } from './imports';
/** ... */
export type MyPlugin = DefinePlugin<UserConfig, Config, never, MyPluginImports>;import { myPluginImports } from './imports';
export const defaultConfig: MyPlugin['Config'] = { config: { ... }, handler, imports: myPluginImports, name: 'my-plugin',};export const handler: MyPlugin['Handler'] = ({ plugin }) => { const { myLib } = plugin.imports; // use myLib as a typed reference when building nodes};Usage
Section titled UsageOnce youre 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 OutputPutting all of this together will generate the following my-plugin.gen.ts file.
export const user = 'Stan Smith';Congratulations! Youve successfully created your own plugin!
Examples
You can view live examples on StackBlitz or on GitHub.