Creating Plugins
This guide walks you through creating a TeamIDE plugin from scratch.
Prerequisites
- Node.js 18+ installed
- Basic knowledge of Vue 3 and TypeScript
- A GitHub or GitLab account (for publishing)
Project Structure
A minimal plugin has this structure:
my-plugin/
teamide-plugin.json # Plugin manifest (required)
features.json # Toggleable features (optional)
dist/
index.js # Compiled bundle (required)
src/
index.ts # Entry point
components/
Navigation.vue # Left panel component
Main.vue # Center panel component
Context.vue # Right panel component (optional)
package.json
vite.config.ts # Build configuration
Step 1: Create the Plugin Manifest
Create teamide-plugin.json at the repository
root:
{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"entry": "./dist/index.js",
"description": "A custom TeamIDE plugin",
"icon": "widgets"
}
Important: The id must be
unique across all plugins. Use lowercase with hyphens.
Step 2: Set Up the Project
Initialize your project:
mkdir my-plugin && cd my-plugin
npm init -y
npm install -D vite vue typescript @vitejs/plugin-vue
Create tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"declaration": true,
"declarationDir": "./dist/types",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["ES2020", "DOM"],
"skipLibCheck": true,
"paths": {
"vue": ["./node_modules/vue"],
"pinia": ["./node_modules/pinia"],
"quasar": ["./node_modules/quasar"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Step 3: Configure the Build
Create vite.config.ts:
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: 'src/index.ts',
name: 'MyPlugin',
formats: ['iife'],
fileName: () => 'index.js'
},
outDir: 'dist',
rollupOptions: {
external: ['vue', 'pinia', 'quasar'],
output: {
globals: {
vue: 'Vue',
pinia: 'Pinia',
quasar: 'Quasar'
},
// Ensure the module definition is exported correctly
footer: `
if (typeof exports !== 'undefined' && MyPlugin && MyPlugin.default) {
exports.default = MyPlugin.default;
}
`
}
}
}
});
Key points: - Build as IIFE format (not ESM)
- Mark vue, pinia, quasar
as external (provided by TeamIDE) - Export the module definition
as default
Step 4: Create Components
Navigation Component (Left Panel)
Create src/components/Navigation.vue:
<template>
<div class="my-plugin-nav q-pa-md">
<div class="text-h6 q-mb-md">My Plugin</div>
<q-list>
<q-item clickable v-ripple @click="doSomething">
<q-item-section avatar>
<q-icon name="star" />
</q-item-section>
<q-item-section>Action Item</q-item-section>
</q-item>
</q-list>
</div>
</template>
<script setup lang="ts">
function doSomething() {
console.log('Action clicked!');
}
</script>
<style scoped>
.my-plugin-nav {
height: 100%;
}
</style>
Main Component (Center Panel)
Create src/components/Main.vue:
<template>
<div class="my-plugin-main q-pa-lg">
<h1>Welcome to My Plugin</h1>
<p>This is the main content area.</p>
<q-btn
color="primary"
label="Open a File"
@click="openFile"
/>
</div>
</template>
<script setup lang="ts">
async function openFile() {
// Use the TeamIDE API to open a file
if (window.__teamide) {
await window.__teamide.openFile('/README.md');
}
}
</script>
<style scoped>
.my-plugin-main {
height: 100%;
overflow: auto;
}
</style>
Context Component (Right Panel - Optional)
Create src/components/Context.vue:
<template>
<div class="my-plugin-context q-pa-md">
<div class="text-subtitle1 q-mb-md">Details</div>
<p>Additional context information goes here.</p>
</div>
</template>
<script setup lang="ts">
// Optional: Add logic for the context panel
</script>
Step 5: Create the Entry Point
Create src/index.ts:
import { defineComponent } from 'vue';
import Navigation from './components/Navigation.vue';
import Main from './components/Main.vue';
import Context from './components/Context.vue';
// Define the module for TeamIDE
const moduleDefinition = {
id: 'my-plugin',
name: 'My Plugin',
icon: 'widgets',
version: '1.0.0',
// Required components
navigationComponent: Navigation,
mainComponent: Main,
// Optional components
contextComponent: Context,
// Optional lifecycle hooks
onRegister() {
console.log('[My Plugin] Registered');
},
onActivate() {
console.log('[My Plugin] Activated');
},
onDeactivate() {
console.log('[My Plugin] Deactivated');
}
};
export default moduleDefinition;
Step 6: Add TypeScript Declarations
Create src/types.d.ts for the TeamIDE API:
interface PluginAPI {
openFile: (path: string, projectId?: string) => Promise<void>;
getProjectId: () => string | null;
switchModule: (moduleId: string) => void;
getGitHubToken: () => string | null;
getGitHubUsername: () => string | null;
getPluginSettings: (pluginId: string) => Promise<Record<string, unknown> | null>;
savePluginSettings: (pluginId: string, settings: Record<string, unknown>) => Promise<void>;
}
declare global {
interface Window {
__teamide?: PluginAPI;
}
}
export {};
Step 7: Build the Plugin
Add build scripts to package.json:
{
"scripts": {
"build": "vite build",
"dev": "vite build --watch"
}
}
Build your plugin:
npm run build
This creates dist/index.js - your plugin
bundle.
Step 8: Test Locally
Option A: Add from Local Path
- Build your plugin:
npm run build - In TeamIDE, go to Settings > Plugins
- Click “Add Local Plugin”
- Enter the full path to your plugin folder
- Click Add
Option B: Add from Workspace
- Clone your plugin repository in TeamIDE
- Build the plugin (use the terminal module)
- In Settings > Plugins, select your repo from the workspace list
Step 9: Publish to GitHub
- Ensure
dist/index.jsis committed (don’t gitignore it) - Push to GitHub
- In TeamIDE Settings > Plugins, add the GitHub URL
Example URL:
https://github.com/yourusername/my-plugin
Adding Toggleable Features
Create features.json to let users toggle plugin
behaviors:
{
"module": {
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0"
},
"features": [
{
"id": "my-plugin-auto-refresh",
"name": "Auto Refresh",
"description": "Automatically refresh data every 30 seconds",
"default": true,
"category": "general"
},
{
"id": "my-plugin-dark-mode",
"name": "Dark Mode",
"description": "Use dark theme for plugin UI",
"default": false,
"category": "advanced"
}
]
}
Check feature status in your code:
import { isFeatureEnabled } from '@teamide/features'; // Pseudo-code
// In your component
const autoRefresh = isFeatureEnabled('my-plugin-auto-refresh');
Using Plugin Settings
Persist user preferences with the settings API:
// Save settings
await window.__teamide?.savePluginSettings('my-plugin', {
theme: 'dark',
refreshInterval: 30
});
// Load settings
const settings = await window.__teamide?.getPluginSettings('my-plugin');
if (settings) {
console.log('Theme:', settings.theme);
}
Module Lifecycle
Plugins can hook into the module lifecycle:
| Hook | When Called | Use Case |
|---|---|---|
onRegister |
Plugin loads at app startup | Initialize services, register actions |
onActivate |
User clicks the plugin tab | Load data, start timers |
onDeactivate |
User switches to another tab | Cleanup, stop timers |
const moduleDefinition = {
// ... other fields
onRegister() {
// Called once when plugin loads
// Good for: registering global actions, setting up services
},
onActivate() {
// Called when user switches TO this plugin
// Good for: fetching fresh data, starting intervals
},
onDeactivate() {
// Called when user switches AWAY from this plugin
// Good for: cleanup, stopping intervals
}
};
Render Modes
Plugins can render in two modes:
Default Mode (Standard Tab)
The plugin shows as a full tab with navigation, main, and context panels:
const moduleDefinition = {
id: 'my-plugin',
renderMode: 'default', // or omit (default is 'default')
// ...
};
Overlay Mode (Bottom Panel)
The plugin renders as a bottom overlay, like the Terminal module. The tab acts as a toggle:
const moduleDefinition = {
id: 'my-overlay-plugin',
renderMode: 'overlay',
mainComponent: OverlayContent, // Renders in bottom panel
// navigationComponent not used in overlay mode
};
Best Practices
Do
- Use Quasar components for consistent UI
- Handle errors gracefully
- Clean up resources in
onDeactivate - Use the Plugin API instead of direct DOM manipulation
- Keep bundle size small
Don’t
- Bundle Vue, Pinia, or Quasar (they’re provided)
- Access internal TeamIDE components or stores
- Modify global state outside your plugin
- Make blocking network calls in lifecycle hooks
Example Plugins
Minimal Plugin
A plugin with just a main component:
import { defineComponent, h } from 'vue';
export default {
id: 'minimal-plugin',
name: 'Minimal',
icon: 'star',
version: '1.0.0',
navigationComponent: defineComponent({
setup() {
return () => h('div', { class: 'q-pa-md' }, 'Navigation');
}
}),
mainComponent: defineComponent({
setup() {
return () => h('div', { class: 'q-pa-lg' }, 'Hello from Minimal Plugin!');
}
})
};
Plugin with State
Using Pinia for state management:
import { defineStore } from 'pinia';
import { defineComponent, h, computed } from 'vue';
// Define a store for your plugin
const useMyStore = defineStore('my-plugin-store', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++;
}
}
});
// Use it in your component
const Main = defineComponent({
setup() {
const store = useMyStore();
return () => h('div', { class: 'q-pa-lg' }, [
h('p', `Count: ${store.count}`),
h('button', { onClick: () => store.increment() }, 'Increment')
]);
}
});
Troubleshooting
“Plugin does not export a default ModuleDefinition”
Ensure your entry file exports the module definition as
default:
export default moduleDefinition;
And your Vite config includes the footer to set
exports.default.
Components not rendering
- Check that components are valid Vue components
- Verify the bundle built successfully
- Look for JavaScript errors in the console
Styles not applying
Use scoped styles or prefix your class names to avoid conflicts:
<style scoped>
.my-plugin-container { /* styles */ }
</style>
API methods return null
getProjectId()returnsnullif no repository is selectedgetGitHubToken()returnsnullif no GitHub account is connected
Related
- Plugin System - Plugin system overview
- Plugin API Reference - Complete API documentation
Changelog
| Date | Change |
|---|---|
| 2026-02-02 | Initial documentation |