Creating custom plugins
Write your own plugins to extend Kit clients
Plugins are small, focused functions that extend a Kit client with new capabilities. This page walks through writing a plugin from scratch, declaring its prerequisites, handling asynchronous setup, registering cleanup logic, and composing several plugins into a higher-level bundle.
The ClientPlugin shape
A plugin is a function that takes a client and returns a new client object. The recommended way to add properties is the extendClient(client, additions) helper from @solana/kit, which preserves property descriptors (getters, symbol-keyed properties) that a plain object spread would flatten.
Wrapping the plugin in a small factory function (apple() rather than apple) is the convention across Kit plugins. It gives you somewhere to accept configuration without changing the shape of the function returned to .use(...).
Add prerequisites with TypeScript generics
Plugins can require capabilities from the input client by constraining the input type. If a consumer applies your plugin in the wrong order, TypeScript reports a compile-time error rather than letting it fail at runtime.
The same mechanism is what makes the official plugins compose so cleanly: airdropPayer() requires ClientWithPayer & ClientWithAirdrop, solanaRpc(...) requires ClientWithPayer, and so on. Adopting the same pattern in your own plugins keeps users honest about the order of operations.
Use the standard interfaces
Whenever possible, declare your prerequisites and additions using the standard interfaces from @solana/plugin-interfaces (re-exported from @solana/kit). This lets your plugin compose with any other plugin that targets the same shape, regardless of its package.
The most common interfaces are:
ClientWithPayerandClientWithIdentityfor signer roles.ClientWithRpc<TApi>andClientWithRpcSubscriptions<TApi>for transports.ClientWithAirdropandClientWithGetMinimumBalancefor funding helpers.ClientWithTransactionPlanningandClientWithTransactionSendingfor the transaction lifecycle.
If your plugin's capability does not match any existing interface, define your own type and export it. If you think the capability is generally useful and there is a gap in the standard interfaces, consider opening a PR against anza-xyz/kit so other plugins can adopt it too. Treating capabilities as named interfaces makes them addressable from other code without naming the specific plugin that installed them.
Asynchronous plugins
Plugins can return a Promise<Client> instead of a Client. The .use(...) chain awaits async plugins automatically, so you only need a single await at the end of the chain.
Async and sync plugins can be mixed freely in the same chain. Once any async plugin is involved, the .use(...) chain returns an AsyncClient that you await once at the end.
Release resources on disposal
Plugins that hold disposable resources — WebSocket connections, intervals, file handles — can register a cleanup function with withCleanup(...). The returned client implements Disposable, so a using declaration runs the cleanup when the client goes out of scope.
withCleanup chains existing dispose logic, so you can call it more than once across plugins without overriding earlier cleanup. Reach for it whenever your plugin owns a resource the user would otherwise have to remember to release manually.
Compose smaller plugins into a bundle
A bundle plugin is just a regular plugin that applies several smaller plugins one after another. The pipe(...) helper from @solana/kit makes the composition concise and the resulting type predictable.
The bundle accepts an existing client rather than calling createClient() itself. That keeps your bundle composable: callers stay in control of their own client and can apply additional plugins before or after yours. It is also what lets the official solanaRpc, solanaDevnetRpc, solanaLocalRpc, and litesvm bundles drop into any client the same way a single plugin would.
Publish your plugin
A few small conventions go a long way once a plugin lands on npm:
- Name the factory function after the capability it installs (e.g.
signer,rpcAirdrop,tokenProgram) rather than the package, and make sure it reads well at the call site —use(tokenProgram())should sound like the sentence you would write to describe what it does. - Document the standard interfaces your plugin requires and provides; this is what other plugin authors will read first.
- Keep the public surface minimal — one factory function per concept — and prefer composition over bigger plugins that try to do everything.
Once your plugin is published, open a PR against anza-xyz/kit to be listed on the Available plugins page so other developers can find it.
Next steps
- Plugins — revisit the client model and standard interfaces.
- Available plugins — see how published plugins describe themselves.
- Generating program plugins — let Codama write a program plugin for you.