Skip to content

Getting Started with Plugin Development ​

This guide will walk you through creating your first Gunshi plugin, from the simplest possible plugin to more advanced patterns with extensions and decorators.

Your First Minimal Plugin ​

Let's start with the absolute minimum (no extension) - a plugin that simply logs when it's loaded:

plugin.js
js
import { plugin } from 'gunshi/plugin'

// The simplest possible plugin
export default plugin({
  id: 'hello',
  name: 'Hello Plugin',
  setup: ctx => {
    console.log('Hello from plugin!')
  }
})

Use it in your CLI:

cli.js
js
import { cli } from 'gunshi'
import hello from './plugin.js'

const entry = () => {}

await cli(process.argv.slice(2), entry, {
  plugins: [hello]
})

TIP

The example fully code is here.

Run your application with plugin:

sh
# Run the entry with plugin
node cli.js

Hello from plugin!

This plugin:

  • Has a unique id for identification
  • Has a human-readable name
  • Runs its setup function during plugin initialization
  • Doesn't extend the command context

Adding Global Options ​

Let's create a plugin that adds a global --debug option to all commands:

plugin.js
js
import { plugin } from 'gunshi/plugin'

export default plugin({
  id: 'debug',
  name: 'Debug Plugin',

  setup: ctx => {
    // Add a global option available to all commands
    ctx.addGlobalOption('debug', {
      type: 'boolean',
      short: 'd',
      description: 'Enable debug output'
    })
  }
})

Now all commands have access to --debug:

cli.js
js
import { cli, define } from 'gunshi'
import debug from './plugin.js'

const command = define({
  name: 'build',
  run: ctx => {
    if (ctx.values.debug) {
      console.log('Debug mode enabled')
      console.log('Context:', ctx)
    }
    console.log('Building...')
  }
})

await cli(process.argv.slice(2), command, {
  plugins: [debug]
})

TIP

The example fully code is here.

Run your application with plugin:

sh
# Run command with debug option
node cli.js --debug

Debug mode enabled
Context: ...
...
Building ...

Adding Sub-Commands ​

Plugins can register sub-commands that become available to the CLI:

plugin.js
js
import { plugin } from 'gunshi/plugin'

export default plugin({
  id: 'tools',
  name: 'Developer Tools Plugin',

  setup: ctx => {
    // Add a new sub-command
    ctx.addCommand('clean', {
      name: 'clean',
      description: 'Clean build artifacts',
      args: {
        cache: {
          type: 'boolean',
          description: 'Also clear cache',
          default: false
        }
      },
      run: ctx => {
        console.log('Cleaning build artifacts...')
        if (ctx.values.cache) {
          console.log('Clearing cache...')
        }
        console.log('Clean complete!')
      }
    })

    // Add another sub-command
    ctx.addCommand('lint', {
      name: 'lint',
      description: 'Run linter',
      run: ctx => {
        console.log('Running linter...')
        console.log('No issues found!')
      }
    })
  }
})

Now your CLI has additional commands:

cli.js
js
import { cli, define } from 'gunshi'
import tools from './plugin.js'

// Main command
const command = define({
  name: 'build',
  run: ctx => console.log('Building project...')
})

await cli(process.argv.slice(2), command, {
  plugins: [tools]
})

TIP

The example fully code is here.

Run your application with the new sub-commands:

sh
# Run main command
node cli.js
Building project...

# Run plugin's sub-command
node cli.js clean
Cleaning build artifacts...
Clean complete!

# With arguments
node cli.js clean --cache
Cleaning build artifacts...
Clearing cache...
Clean complete!

# Run another sub-command
node cli.js lint
Running linter...
No issues found!

Advanced Plugin Features ​

Beyond basic setup and global options, plugins can provide much more powerful functionality:

Extensions ​

Plugins can extend the command context with new functionality that all commands can use.

js
// Simple example - adding logging functionality
export default plugin({
  id: 'logger',
  extension: () => ({
    log: msg => console.log(msg)
  })
})

// Commands can then use: ctx.extensions.logger.log('Hello')

TIP

Extensions are the core feature for sharing functionality between plugins and commands. Learn more in Plugin Extensions.

Decorators ​

Plugins can decorate (wrap) existing functionality to enhance behavior:

js
// Customize how help text is displayed
ctx.decorateUsageRenderer(async (baseRenderer, ctx) => {
  const baseUsage = await baseRenderer(ctx)
  return `${baseUsage}\n\n📚 Documentation: https://example.com/docs`
})

TIP

Decorators allow you to wrap commands, renderers, and more. Learn about all decorator types in Plugin Decorators.

Dependencies ​

Plugins can declare dependencies on other plugins:

js
export default plugin({
  id: 'auth',
  dependencies: ['logger'], // Requires logger plugin
  setup: ctx => {
    // Logger plugin is guaranteed to be loaded
  }
})

TIP

Dependencies ensure plugins load in the correct order. Learn more in Plugin Dependencies.

Summary ​

You've now learned the basics of Gunshi plugin development:

  • Creating minimal plugins with setup functions
  • Adding global options available to all commands
  • Registering sub-commands through plugins
  • Understanding advanced features (extensions, decorators, dependencies)

These fundamentals provide a solid foundation for building more complex plugins.

Next Steps ​

You've created your first Gunshi plugin and learned the fundamental concepts: setup functions, global options, sub-command registration, and basic plugin features.

With these foundations in place, you're ready to understand how plugins integrate with the CLI execution flow.

The next chapter, Plugin Lifecycle, will show you exactly when and how plugins execute during CLI runtime, giving you the knowledge to build more sophisticated plugins that interact with commands at the right moments.

Released under the MIT License.