---
title: "Custom Plugin"
description: "Learn how to create your own Hey API plugin."
url: "https://heyapi.dev/docs/openapi/python/plugins/custom"
---

Caution

Plugin API is in development. The interface might change before it becomes stable. We encourage you to leave feedback on [GitHub](https://github.com/hey-api/hey-api/issues).

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”](#file-structure)

We recommend following the design pattern of the native plugins. You can browse the code on [GitHub](https://github.com/hey-api/hey-api/tree/main/packages/openapi-ts/src/plugins) as you follow this tutorial.

* my-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.

index.ts

```ts
export { defaultConfig, defineConfig } from './config';
export type { MyPlugin } from './types';
```

## Types

[Section titled “Types”](#types)

`index.ts` references two files, so we need to create them. `types.ts` contains the TypeScript interface for your plugin options.

types.ts

```ts
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.Name` sets the plugin’s unique identifier.
* `Plugin.Hooks` allows 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.Exports` add the `includeInEntry` option, which controls whether your plugin’s exports are re-exported from the output entry file.
* `UserConfig` is what users write in their config file. `Config` is the fully resolved shape your handler receives.

## Configuration

[Section titled “Configuration”](#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.

config.ts

```ts
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.

* config

  config.ts

  ```ts
  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);
  ```

* input

  openapi-ts.config.ts

  ```js
  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
      }),
    ],
  };
  ```

* resolved

  ```ts
  {
    user: {
      age: 42,
      name: 'Joe Doe'
    }
  }
  ```

### Dependencies

[Section titled “Dependencies”](#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.

config.ts

```ts
export const defaultConfig: MyPlugin['Config'] = {
  config: { ... },
  dependencies: ['@hey-api/typescript'],
  handler,
  name: 'my-plugin',
};
```

#### Resolving tags

[Section titled “Resolving tags”](#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.

config.ts

```ts
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”](#handler)

The `handler` function generates the actual output. We recommend implementing it in `plugin.ts`.

plugin.ts

```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 “Imports”](#imports)

Imports 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 plugin’s behavior. When a user provides hooks like `$resolvers`, they receive the `plugin` instance and can access `plugin.imports` to reference your plugin’s imports directly.

* imports

  imports.ts

  ```ts
  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>;
  ```

* types

  types.ts

  ```ts
  import type { MyPluginImports } from './imports';


  /** ... */


  export type MyPlugin = DefinePlugin<UserConfig, Config, never, MyPluginImports>;
  ```

* config

  config.ts

  ```ts
  import { myPluginImports } from './imports';


  export const defaultConfig: MyPlugin['Config'] = {
    config: { ... },
    handler,
    imports: myPluginImports,
    name: 'my-plugin',
  };
  ```

* handler

  plugin.ts

  ```ts
  export const handler: MyPlugin['Handler'] = ({ plugin }) => {
    const { myLib } = plugin.imports;
    // use myLib as a typed reference when building nodes
  };
  ```

## Usage

[Section titled “Usage”](#usage)

Once you’re satisfied with your plugin, register it in the [configuration](https://heyapi.dev/docs/openapi/typescript/configuration) file.

openapi-ts.config.ts

```js
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”](#output)

Putting all of this together will generate the following `my-plugin.gen.ts` file.

my-plugin.gen.ts

```ts
export const user = 'Stan Smith';
```

Congratulations! You’ve successfully created your own plugin! 🎉

## Examples

You can view live examples on [StackBlitz](https://stackblitz.com/orgs/github/hey-api/collections/openapi-ts-examples) or on [GitHub](https://github.com/hey-api/hey-api/tree/main/examples).
