Post

Creating a GitHub Copilot Plugin

A practical guide to creating and sharing GitHub Copilot plugins that bundle agents, skills, hooks, MCP servers, and other customizations

Creating a GitHub Copilot Plugin

Overview

Tiago Pascoal and I recently finished a Microsoft Build lab, LAB502: Make GitHub Copilot Work Your Way: Custom Tools, Context and Workflows. The lab walks through a lot of the newer Copilot customization surface: instructions, prompt files, skills, custom agents, hooks, MCP servers, and plugins.

The plugin part is what I wanted to pull out into its own post. A lot of teams start by creating a copilot-instructions.md file, then maybe a custom agent or a skill. That works great when the customization lives in one repo. But once you want to share a curated set of customizations across machines, repos, workshops, or teams, a plugin becomes the more natural packaging model.

I’ll walk through what a plugin actually is, why we packaged the LAB502 customizations as one, and how marketplaces help you share plugins beyond your own machine.

GitHub Copilot plugins are moving quickly. The examples below are based on the current GitHub Copilot CLI plugin model and the Build lab repo. If the command syntax changes, run copilot plugin --help or /help in an interactive Copilot CLI session for the latest options.

What Is a Copilot Plugin?

A GitHub Copilot plugin is an installable package for GitHub Copilot CLI customizations. At minimum, it has a plugin.json manifest. From there, it can bundle one or more of these components:

ComponentWhat it is good forExample from the Build lab
SkillsReusable task-level workflows that Copilot can
discover from the skill description
Uploading a screenshot
to the Community Hub
Custom agentsSpecialized personas with their own instructions,
tools, and MCP server configuration
Capturing a web page
screenshot with Playwright
HooksDeterministic actions that run on Copilot
lifecycle events
Reporting session start, prompt
submitted, tool used, and agent stop events
MCP serversExternal tool gateways for APIs, databases,
internal systems, or browser automation
Playwright MCP used by
the screenshot agent
LSP serversLanguage intelligence for specific file typesHTML language server support
for .html and .htm files
Scripts and assetsImplementation files used by skills,
hooks, agents, or tools
Bash and PowerShell scripts for
cross-platform lab environments

A plugin is not a new customization type to learn. Your skills, hooks, and MCP config stay exactly what they were. The plugin is just the wrapper that lets someone install the whole setup in one command.

The Build Lab Example

In the Build lab, we created a plugin called space-invaders-makers. The lab itself has attendees build a small Space Invaders-style browser game with Copilot, then share screenshots and game artifacts with a Community Hub.

The plugin connects the Copilot experience to that Community Hub:

  • It records Copilot activity through hooks, including session starts, prompts, tool usage, agent stops, and subagent stops
  • It provides a share-screenshot skill that uploads an image file to the hub
  • It provides a web-screenshotter custom agent that uses Playwright MCP to capture a web page screenshot and then share it
  • It includes Bash and PowerShell scripts so it works across macOS, Linux, and Windows lab machines
  • It includes an HTML language server configuration for browser-game files

The plugin is in the lab repo under src/plugins/space-invaders-makers/. The shape looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
space-invaders-makers/
├── plugin.json
├── agents/
│   └── web-screenshotter.agent.md
├── hooks/
│   └── hooks.json
├── scripts/
│   ├── agent-stop.sh / agent-stop.ps1
│   ├── session-start.sh / session-start.ps1
│   ├── subagent-stop.sh / subagent-stop.ps1
│   ├── tool-used.sh / tool-used.ps1
│   └── user-prompt-submitted.sh / user-prompt-submitted.ps1
└── skills/
    └── share-screenshot/
        ├── SKILL.md
        └── scripts/
            └── share-screenshot.sh / share-screenshot.ps1

I like that a plugin does not need to be one giant thing. It can be a thin manifest plus the same kinds of small files you would have written anyway.

Creating the Plugin Manifest

The plugin.json file tells Copilot what the plugin is and where its components live.

Here is a simplified version of the Build lab plugin manifest shape:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "name": "space-invaders-makers",
  "description": "Tracks lab activity with hooks and helps users capture and share web screenshots through the Community Hub",
  "version": "1.0.0",
  "author": {
    "name": "Build 2026 Lab"
  },
  "skills": [
    "skills/share-screenshot"
  ],
  "agents": "agents/",
  "hooks": "hooks/hooks.json",
  "lspServers": {
    "html": {
      "command": "vscode-html-language-server",
      "args": ["--stdio"],
      "fileExtensions": {
        ".html": "html",
        ".htm": "html"
      }
    }
  }
}

You technically only need a name, but I would still add a description, version, and author. It makes the plugin much easier to understand when someone browses or installs it.

Here’s what the main manifest fields do:

FieldPurpose
nameKebab-case plugin name
descriptionShort description shown when browsing or listing plugins
versionSemantic version of the plugin
authorPlugin author metadata
agentsPath or paths to directories containing .agent.md files
skillsPath or paths to skill directories containing SKILL.md files
hooksPath to a hook configuration file, or inline hook configuration
mcpServersPath to MCP config, or inline MCP server definitions
lspServersPath to LSP config, or inline LSP server definitions

You do not have to write this file from memory. Two things make it much easier:

  • Add a $schema field pointing at the Open Plugin Spec v1.0.0 schema (grab the canonical URL from the plugin reference). Once it is there, VS Code’s built-in JSON support gives you autocomplete, hover descriptions, and inline validation as you type, so you pick fields from a dropdown instead of memorizing them.
  • Let Copilot scaffold it. This is a Copilot feature, so the fastest path is to ask Copilot CLI or Copilot in VS Code to stub out the plugin folder, plugin.json, an agent, and a skill, then adjust from there.

For the full list of manifest fields, see the GitHub Copilot CLI plugin reference. For a step-by-step walkthrough, see Creating a plugin for GitHub Copilot CLI.

Adding a Skill

Skills are useful when you want Copilot to have a reusable workflow that can include instructions, scripts, references, and supporting assets. In the lab plugin, the share-screenshot skill uploads a local image file to the Community Hub.

The skill starts with a SKILL.md file. The description matters because Copilot uses it to decide when the skill is relevant.

1
2
3
4
5
6
7
8
9
10
---
name: share-screenshot
description: Use this only when a user explicitly asks to upload or share a screenshot image file to the Community Hub. Resolve the image path before uploading.
argument-hint: Absolute path to the screenshot file to share
---

# Share Screenshot

Upload the provided screenshot file to the Community Hub.
Use the bundled script for the current platform and report the uploaded URL back to the user.

The script can live next to the skill, for example under skills/share-screenshot/scripts/. That keeps the skill portable because the instructions and implementation travel together.

Adding a Custom Agent

The lab also has a web-screenshotter agent. This agent is designed for one job: capture screenshots from web pages, then share them through the skill.

The important design choice is that the agent owns the full workflow. The user does not need to remember the sequence of “open the page, capture the screenshot, find the file, upload the file.” They can ask for the outcome, and the agent can use the plugin-provided pieces to get there.

Here is the kind of frontmatter an agent like that uses:

1
2
3
4
5
6
7
8
9
---
name: web-screenshotter
description: Use this agent when a user asks to capture and share screenshots from one or more web pages.
tools: ['execute', 'playwright/*']
mcp-servers:
  playwright:
    command: npx
    args: ['-y', '@playwright/mcp@latest']
---

I would use the same pattern for other internal workflows: give the agent a narrow job, hand it the tools it needs, and let the plugin provide the scripts or skills it calls.

Adding Hooks

Hooks are for behavior you want to happen reliably around Copilot activity. Unlike a skill or agent, a hook does not rely on the model deciding to use it. It runs on lifecycle events.

In the Build lab plugin, hooks report activity back to the Community Hub. The hook configuration maps lifecycle events to platform-specific scripts. The event names are the PascalCase keys, such as SessionStart, UserPromptSubmit, and PostToolUse.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  "version": 1,
  "hooks": {
    "SessionStart": [
      {
        "type": "command",
        "bash": "./scripts/session-start.sh",
        "powershell": "./scripts/session-start.ps1"
      }
    ],
    "UserPromptSubmit": [
      {
        "type": "command",
        "bash": "./scripts/user-prompt-submitted.sh",
        "powershell": "./scripts/user-prompt-submitted.ps1"
      }
    ],
    "PostToolUse": [
      {
        "type": "command",
        "bash": "./scripts/tool-used.sh",
        "powershell": "./scripts/tool-used.ps1"
      }
    ]
  }
}

This is useful for more than lab telemetry. In a real setup, hooks are a good fit for audit logging, security gates, setting up the environment, or seeding context before the agent starts.

Hooks and bundled scripts run on the user’s machine with the user’s permissions. Review plugin code before installing it, especially SKILL.md, .agent.md, hook configuration, and any scripts called by those files.

Testing a Plugin Locally

You can install a plugin directly from a local path while you are developing it:

1
2
copilot plugin install ./space-invaders-makers
copilot plugin list

Inside an interactive Copilot CLI session, you can verify installed components with commands like:

1
2
3
/plugin list
/agent
/skills list

The two forms are equivalent: copilot plugin ... commands run in your regular shell, while the /plugin ... slash commands run inside an interactive Copilot CLI session. Use whichever matches where you are.

One thing to remember: installed plugin components are cached. If you edit a local plugin after installing it, reinstall the plugin so Copilot picks up your changes.

1
copilot plugin install ./space-invaders-makers

Publishing Through a Plugin Marketplace

This is the part that confused me at first: a Copilot plugin marketplace is not the same thing as GitHub Marketplace for GitHub Apps, and it is not the VS Code Marketplace. In this context, a marketplace is a registry of Copilot CLI plugins. That registry can live in a GitHub repository.

GitHub Copilot CLI includes default marketplaces, currently copilot-plugins and awesome-copilot. You can also add another marketplace by pointing Copilot CLI at a GitHub repository, another Git URL, or a local path.

In the Build lab, attendees add the lab repo as a marketplace:

1
/plugin marketplace add microsoft/Build26-LAB502-make-github-copilot-work-your-way-custom-tools-context-and-workflows

Then they browse and install the plugin from that marketplace:

1
2
3
/plugin marketplace browse community-hub
/plugin install space-invaders-makers@community-hub
/plugin list

The marketplace itself is described with a marketplace.json file. GitHub Docs recommends placing it under .github/plugin/marketplace.json in the repository.

Here is a minimal marketplace entry:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "name": "community-hub",
  "owner": {
    "name": "Build 2026 Lab"
  },
  "plugins": [
    {
      "name": "space-invaders-makers",
      "description": "Tracks lab activity and helps users capture and share screenshots",
      "version": "1.0.0",
      "source": "./src/plugins/space-invaders-makers"
    }
  ]
}

You can add more marketplace and plugin metadata, but this is the basic shape: name the marketplace, identify the owner, and list the plugins with their source paths.

The .github/plugin/ here is just a directory inside a regular repository. It is not the special organization-level .github or .github-private repository, and the marketplace does not have to live in either of those. Any repo works, and a dedicated repo is common, for example github/copilot-plugins keeps its marketplace.json at .github/plugin/marketplace.json. Copilot CLI treats the repo as a marketplace as soon as it finds that file (it also checks .plugin/, and .claude-plugin/ for Claude Code compatibility).

One thing worth setting expectations on: self-service marketplace browsing with copilot plugin marketplace add and browse is a Copilot CLI flow, and simply adding a marketplace.json to a repo does not create a browsable plugin catalog in the VS Code UI. There are really two ways plugins reach users: they install them by hand with the CLI, or an enterprise pushes them centrally. Enterprise plugin standards let an admin define known marketplaces and default-enabled plugins that apply to users across supported clients when they authenticate. More on that below.

Once that marketplace file exists in a repo, users can add the repo and install the plugin by name:

1
2
3
copilot plugin marketplace add OWNER/REPO
copilot plugin marketplace browse MARKETPLACE-NAME
copilot plugin install PLUGIN-NAME@MARKETPLACE-NAME

You can also install directly without a marketplace:

1
2
3
copilot plugin install OWNER/REPO
copilot plugin install OWNER/REPO:PATH/TO/PLUGIN
copilot plugin install ./local-plugin-folder

For a team, I like the marketplace approach because it gives people a discoverable catalog instead of making them remember repo paths. It also gives you a natural place to describe supported plugins, ownership, versions, and installation instructions.

Where Enterprise Plugin Standards Fit

There is a separate but related GitHub admin feature called enterprise plugin standards. This is the piece that answers “how do I get plugins in front of my whole org without everyone running install commands.” I covered where these governance settings can live in my post on .github and .github-private repositories.

The mechanism is a managed-settings.json file, which an enterprise can source from a designated .github-private repository. For plugins, it supports three keys:

KeyWhat it does
extraKnownMarketplacesAdds marketplaces users can browse and install from
enabledPluginsAuto-installs (or blocks) specific plugins, keyed as PLUGIN-NAME@MARKETPLACE-NAME
strictKnownMarketplacesRestricts installs to only the marketplaces the enterprise lists

When a user authenticates in a supported client, the client reads managed-settings.json and applies these policies to the session. So this is the path where a default-enabled plugin can show up for a user without them adding a marketplace first. All three plugin keys are supported in Copilot CLI, VS Code, and the GitHub Copilot app, per the enterprise managed settings reference.

This is also the only way plugins reach VS Code. There is no self-service plugin install in the VS Code UI, so a plugin only lands in VS Code when an enterprise pushes it through managed-settings.json. Self-service installs (copilot plugin install and marketplace add) stay on the CLI side.

So if you are building a plugin for yourself or a workshop, start with the plugin and optionally a marketplace. If you are rolling this out across an enterprise, use managed-settings.json in your .github-private governance repo to publish the marketplaces and default plugins centrally.

A Practical Creation Checklist

When creating your own plugin, I would start with this checklist:

  1. Pick a workflow that has multiple pieces and is worth sharing as a unit.
  2. Create a plugin folder with plugin.json at the root.
  3. Add one component at a time: a skill, an agent, hooks, MCP config, or LSP config.
  4. Keep scripts close to the component that uses them.
  5. Test with copilot plugin install ./your-plugin.
  6. Reinstall after local changes because plugin content is cached.
  7. Review the installed behavior in Copilot CLI with /plugin list, /agent, and /skills list.
  8. Add a marketplace.json when you are ready to share the plugin with others.
  9. Document environment variables, required tools, and security considerations.

For the Build lab, the workflow was “help attendees build, capture, and share browser games while reporting activity to a Community Hub.” For an internal platform team, it might be “open a production incident workspace,” “generate a service migration plan,” “validate repository compliance,” or “package our deployment review workflow.”

Summary

Copilot plugins are the packaging layer for Copilot customizations. They let you take agents, skills, hooks, MCP server configuration, LSP configuration, scripts, and supporting files and ship them as a single installable unit.

The Build lab plugin is a good example because it does something concrete: it connects Copilot activity to a Community Hub, adds a screenshot-sharing workflow, and gives users a specialized agent that can complete the capture-and-upload flow. That is where plugins earn their keep, as a small collection of customizations that work better together than any one of them would on its own.

The mental model I keep coming back to:

  • Use instructions for always-on repo context
  • Use skills for reusable task workflows
  • Use custom agents for specialized roles with specific tools
  • Use hooks for deterministic behavior around Copilot activity
  • Use MCP for external systems and tools
  • Use plugins when you want to distribute those pieces together
  • Use marketplaces when you want other people to discover and install them easily

That gives you a path from a one-repo customization to a shareable team or workshop experience without turning the whole thing into a custom extension or app.

This post is licensed under CC BY 4.0 by the author.