---
title: Bamboo Integration Hooks
description: Leveraging hooks in Bamboo to create custom functionality.
---

Bamboo hooks can be used to add new functionality or modify existing behavior during certian parts of the compiler
lifecycle.

Hooks are mostly callbacks, registered through `plugins` — a plugin is a `name` and a `hooks` object.

They used to be registrable two ways: through `plugins`, or through a bare `hooks` key on the config, which was treated
as a nameless plugin appended after the rest. That gave one mechanism two spellings and an ordering rule you had to
know, and left every diagnostic about a hook with no name to print for the config's own set. Your own hooks are a plugin
like any other now, so ordering is just the order of the array.

See the [Reference](#reference) below for the full list of hooks and their signatures.

## Examples

### Prefixing token names

This is especially useful when migrating from other css-in-js libraries, [like Stitches.](/docs/migration/stitches)

```ts
import { defineConfig } from '@bamboocss/dev'

export default defineConfig({
  // ...
  plugins: [
    {
      name: 'my-app',
      hooks: {
        'tokens:created': ({ configure }) => {
          configure({
            formatTokenName: (path) => '$' + path.join('-'),
          })
        },
      },
    },
  ],
})
```

### Customizing the hash function

When using the [`hash: true`](/docs/concepts/writing-styles) config property, you can customize the function used to
hash the classnames.

```ts
export default defineConfig({
  // ...
  hash: true,
  plugins: [
    {
      name: 'my-app',
      hooks: {
        'utility:created': ({ configure }) => {
          configure({
            toHash: (paths, toHash) => {
              const stringConds = paths.join(':')
              const splitConds = stringConds.split('_')
              const hashConds = splitConds.map(toHash)
              return hashConds.join('_')
            },
          })
        },
      },
    },
  ],
})
```

### Modifying the config

Here's an example of how to leveraging the provided `utils` functions in the `config:resolved` hook to remove the
`float` pattern from the resolved config.

```ts
import { defineConfig } from '@bamboocss/dev'

export default defineConfig({
  // ...
  plugins: [
    {
      name: 'my-app',
      hooks: {
        'config:resolved': ({ config, utils }) => {
          return utils.omit(config, ['patterns.float'])
        },
      },
    },
  ],
})
```

### Modifying presets

You can use the `preset:resolved` hook to modify presets after they are resolved. This is useful for customizing or
filtering out parts of a preset.

```ts
import { defineConfig } from '@bamboocss/dev'

export default defineConfig({
  // ...
  plugins: [
    {
      name: 'my-app',
      hooks: {
        'preset:resolved': ({ utils, preset, name }) => {
          if (name === '@bamboocss/preset-bamboo') {
            return utils.omit(preset, ['theme.tokens.colors', 'theme.semanticTokens.colors'])
          }
          return preset
        },
      },
    },
  ],
})
```

### Configuring JSX extraction

Use the `matchTag` / `matchTagProp` functions to customize the way Bamboo extracts your JSX.

This can be especially useful when working with libraries that have properties that look like CSS properties but are not
and should be ignored.

Let's see a Radix UI example where the `Select.Content` component has a `position` property that should be ignored:

```js
// Here, the `position` property will be extracted because `position` is a valid CSS property, but we don't want that
<Select.Content position="popper" sideOffset={5}>
```

```ts
export default defineConfig({
  // ...
  plugins: [
    {
      name: 'my-app',
      hooks: {
        'parser:before': ({ configure }) => {
          configure({
            // ignore the Select.Content entirely
            matchTag: (tag) => tag !== 'Select.Content',
            // ...or specifically ignore the `position` property
            matchTagProp: (tag, prop) => tag === 'Select.Content' && prop !== 'position',
          })
        },
      },
    },
  ],
})
```

### Transforming the generated css

`cssgen:done` runs just before the CSS is written to disk or injected by the PostCSS plugin. It receives the artifact
being written and the CSS as a string, and whatever you return replaces it — return nothing to leave it alone.

Rewriting asset URLs so the stylesheet can be served from a CDN:

```ts file="bamboo.config.ts"
import { defineConfig } from '@bamboocss/dev'

export default defineConfig({
  // ...
  plugins: [
    {
      name: 'my-app',
      hooks: {
        'cssgen:done': ({ artifact, content }) => {
          if (artifact === 'styles.css') {
            return content.replaceAll('url(/assets/', 'url(https://cdn.example.com/assets/')
          }
        },
      },
    },
  ],
})
```

<Callout type="warning">
  Do not use this to strip unused tokens or keyframes. [`prune.tokens`](/docs/references/config#prune) and
  [`prune.keyframes`](/docs/references/config#prune) already do it, are on by default, and are safe in a way a scan of
  the finished CSS cannot be — they keep the declarations `token()` and `token.value()` resolve to at runtime, which
  nothing in the stylesheet refers to. [`preflight.prune`](/docs/references/config#preflightprune) covers the reset.
</Callout>

## Sharing hooks

A plugin is already shareable — it is a plain object with a `name` and a `hooks` object, so exporting one from a package
is all there is to it.

> Plugins differ from `presets` as they can't be extended, and they are called in sequence in the order they are defined
> in the `plugins` array.

```ts
import { defineConfig } from '@bamboocss/dev'

export default defineConfig({
  // ...
  plugins: [
    {
      name: 'token-format',
      hooks: {
        'tokens:created': ({ configure }) => {
          configure({
            formatTokenName: (path) => '$' + path.join('-'),
          })
        },
      },
    },
  ],
})
```

## Reference

```ts
export interface BambooHooks {
  /**
   * Called when the config is resolved, after all the presets are loaded and merged.
   * This is the first hook called, you can use it to tweak the config before the context is created.
   */
  'config:resolved': (args: ConfigResolvedHookArgs) => MaybeAsyncReturn<void | ConfigResolvedHookArgs['config']>
  /**
   * Called when each preset is resolved, allowing modification of individual presets.
   * This hook is called for each preset during the resolution process, before they are merged together.
   */
  'preset:resolved': (args: PresetResolvedHookArgs) => MaybeAsyncReturn<void | PresetResolvedHookArgs['preset']>
  /**
   * Called when the token engine has been created
   */
  'tokens:created': (args: TokenCreatedHookArgs) => MaybeAsyncReturn
  /**
   * Called when the classname engine has been created
   */
  'utility:created': (args: UtilityCreatedHookArgs) => MaybeAsyncReturn
  /**
   * Called when the Bamboo context has been created and the API is ready to be used.
   */
  'context:created': (args: ContextCreatedHookArgs) => void
  /**
   * Called when the config file or one of its dependencies (imports) has changed.
   */
  'config:change': (args: ConfigChangeHookArgs) => MaybeAsyncReturn
  /**
   * Called after reading the file content but before parsing it.
   * You can use this hook to transform the file content to a tsx-friendly syntax so that Bamboo's parser can parse it.
   * You can also use this hook to parse the file's content on your side using a custom parser, in this case you don't have to return anything.
   */
  'parser:before': (args: ParserResultBeforeHookArgs) => string | void
  /**
   * Called after the file styles are extracted and processed into the resulting ParserResult object.
   * You can also use this hook to add your own extraction results from your custom parser to the ParserResult object.
   */
  'parser:after': (args: ParserResultAfterHookArgs) => void
  /**
   * Called right before writing the codegen files to disk.
   * You can use this hook to tweak the codegen files before they are written to disk.
   */
  'codegen:prepare': (args: CodegenPrepareHookArgs) => MaybeAsyncReturn<void | Artifact[]>
  /**
   * Called after the codegen is completed
   */
  'codegen:done': (args: CodegenDoneHookArgs) => MaybeAsyncReturn
  /**
   * Called right before adding the design-system CSS (global, static, preflight, tokens, keyframes) to the final CSS
   * Called right before writing/injecting the final CSS (styles.css) that contains the design-system CSS and the parser CSS
   * You can use it to tweak the CSS content before it's written to disk or injected through the postcss plugin.
   */
  'cssgen:done': (args: CssgenDoneHookArgs) => string | void
  /**
   * Called when CSS needs to be optimized. Use this hook to replace the default PostCSS-based optimizer
   * with a custom one (e.g. LightningCSS).
   * Return the optimized CSS string, or void to fall through to the default PostCSS optimizer.
   */
  'css:optimize': (args: CssOptimizeHookArgs) => string | void
}
```
