Plugin Interface
In Arto, plugins allow you to customize or extend the class-building pipeline. Each plugin can inspect or modify classes at different stages, or even add final callbacks for post-processing. This page explains the structure of the Plugin Interface and how to work with the global PluginRegistry (pluginHub).
Plugin Type
ts
export interface Plugin<
TVariants extends Record<string, VariantValue> = Record<string, VariantValue>,
TStates extends string = string,
TContext = unknown,
> {
/**
* A unique identifier for the plugin.
*/
readonly id: string
/**
* Which stage to run in: 'before', 'core', or 'after'.
* Defaults to 'core' if omitted.
*/
stage?: PluginStage
/**
* Execution order within the chosen stage (lower means earlier).
* Defaults to 0 if omitted.
*/
order?: number
/**
* The function that receives a ClassNameBuilder,
* allowing you to manipulate or inspect classes.
*/
apply(builder: ClassNameBuilder<TVariants, TStates, TContext>): void | Promise<void>
}Fields
id: A string that uniquely identifies the plugin.stage: Determines when the plugin should run within Arto’s build process:'before': Run before internal (core) logic and other'core'plugins.'core': Run alongside Arto’s main built-in plugins.'after': Run after the core plugins finish.
order: A numeric priority within each stage. Lower = earlier.apply(builder): The main callback where you modify or inspect classes using the ClassNameBuilder instance.
Example Plugin
ts
import type { Plugin } from 'arto'
export const MyExamplePlugin: Plugin = {
id: 'my/example-plugin',
stage: 'core',
order: 5,
apply(builder) {
// Run logic at the 'core' stage with priority 5
builder.addPostCoreCallback(() => {
// Possibly do something with the final classes
const allClasses = builder.getAllClasses()
console.log('[MyExamplePlugin] Classes so far:', allClasses.join(' '))
})
},
}In this plugin:
- The
idis'my/example-plugin'. stageis'core', meaning it runs with Arto’s built-in plugins.orderis5, so it might run after a plugin withorder = 0, but before one withorder = 10.- The plugin uses
builder.addPostCoreCallback(...)to inspect classes after all core logic is done but before any'after'stage plugins.
Plugin Stages
Arto executes plugins in three stages:
before– Used for tasks that need to happen prior to the main class merges (e.g., logging, early transforms).core– The main stage where built-in plugins (base, variants, states, rules) operate. Usually where you place custom plugins if they directly manipulate class buckets.after– Great for final touches, cleanup, or validating the final list of classes (linting, conflict checks, etc.).
PluginRegistry (pluginHub)
ts
import { pluginHub } from 'arto'
pluginHub.register(MyExamplePlugin)
pluginHub.unregister('my/example-plugin')
pluginHub.clear()
pluginHub.getPlugins()
// etc.Methods
register(plugin): Add or replace a plugin with the sameid.unregister(id): Remove a plugin by itsid.clear(): Remove all registered plugins.getPlugins(): Return an array of all plugins (unsorted).
Any plugin registered here is global and applied to all Arto configs unless removed.
Local Plugins
If you only want a plugin for a specific config, provide it as the second argument to arto():
ts
import { arto } from 'arto'
import { MyExamplePlugin } from './my-plugin'
const myConfig = arto(
{
className: 'some-base-classes',
// ...
},
[MyExamplePlugin],
)This merges your local plugin(s) with all globally registered plugins, sorted by stage and order.
Summary
- A plugin must define:
id(string)stage('before' | 'core' | 'after')order(number)apply(builder)method
- You can register plugins globally with
pluginHubor pass them locally toarto(...). - Stages and order determine execution flow.
ClassNameBuilderis passed toapply(...)so you can add/replace/clear classes, add callbacks, or read the context.
Check out ClassNameBuilder Documentation for more details on available methods to manipulate classes.