This guide walks you through creating, testing, and publishing your first Binder plugin.
- Node.js 18+
- A public GitHub repository
- Binder installed
Create a new public repository on GitHub. The repository name becomes your plugin's default ID, so use kebab-case (e.g. plugin-file-tree).
mkdir my-plugin && cd my-plugin
npm init -y
npm install --save-dev vite @vitejs/plugin-react typescript react react-dom
npm install --save-dev @types/react @types/react-domAdd the Plugin SDK types. Since the SDK is included in Binder's build, you reference it as a type-only package:
npm install --save-dev @binder/plugin-sdkThe SDK package contains only TypeScript interfaces — no runtime code. React is bundled with your plugin (not a peer dependency) unless you choose to externalize it.
Create src/index.tsx:
import React, { useState } from 'react'
import type { Plugin, PluginTabProps } from '@binder/plugin-sdk'
function MyTab({ context }: PluginTabProps) {
const { executeCommand, cwd } = context
const [output, setOutput] = useState<string>('')
const run = (cmd: string) => {
executeCommand?.(cmd)
setOutput(`▶ ${cmd}`)
}
return (
<div style={{ padding: '16px', fontFamily: 'monospace', color: '#ccc' }}>
<h3 style={{ margin: '0 0 12px', color: '#ddd' }}>My Plugin</h3>
<p style={{ color: '#666', fontSize: 12 }}>cwd: {cwd ?? 'unknown'}</p>
<button onClick={() => run('ls -la')}
style={{ background: 'rgba(79,195,247,0.1)', border: '1px solid rgba(79,195,247,0.25)',
color: '#4fc3f7', padding: '6px 14px', borderRadius: 5, cursor: 'pointer' }}>
List files
</button>
{output && <pre style={{ marginTop: 12, color: '#888', fontSize: 11 }}>{output}</pre>}
</div>
)
}
const plugin: Plugin = {
id: 'my-plugin', // must be unique; use kebab-case
name: 'My Plugin',
description: 'A minimal example Binder plugin.',
author: 'your-github-handle',
version: '1.0.0',
tabType: 'my-plugin', // the tab type opened by /my-plugin in the terminal
tabTitle: 'my plugin', // text shown on the tab
TabComponent: MyTab,
}
export default pluginCreate vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
lib: {
entry: 'src/index.tsx',
formats: ['es'],
fileName: () => 'index.js',
},
outDir: 'dist',
// Do NOT set rollupOptions.external — bundle React with the plugin
},
})Create tsconfig.json:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"jsx": "react-jsx",
"strict": true,
"noEmit": true
},
"include": ["src"]
}npm run build
# → dist/index.jsTo test in Binder, open the Plugin Store (/plugins), go to the External tab, and paste your repository URL. If you haven't pushed yet, you can temporarily serve the bundle locally and use ngrok or a local IP — but for the best experience, push to GitHub and install directly.
- Push your code including the
dist/folder to GitHub - Make sure your repository is public
- Open Binder →
/plugins→ External tab → paste your GitHub repo URL → Fetch & Install
Your plugin is now installed and its tab type is available.
- API Reference — all interfaces, props, and context methods
- Building a Plugin — advanced topics: slash commands, requirements check, CSS isolation