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.