Skip to content
Work IQ Dev Tools are in preview (v0.20.0). Commands and APIs may change before 1.0. See the installation guide.

Build a Plugin

A plugin is a Microsoft 365 app package that composes a declarative agent, one or more skills, and/or agent connectors (remote MCP servers or references to existing OpenAPI custom connectors) into a single, independently shippable unit. This guide gets you from nothing to a validated, provisioned, and shared plugin — the same outcome the Microsoft Learn “Build plugins for Copilot Cowork” walkthrough targets, reached the wiqd way. If you haven’t yet, read What is a plugin? first for the mental model.

  1. Install wiqd (the CLI, the VS Code extension, and the GitHub Copilot CLI plugin, all in one step):

    Windows (PowerShell):

    Terminal window
    iex "& { $(irm 'https://aka.ms/wiqd/install.ps1') }"

    macOS/Linux:

    Terminal window
    curl -fsSL https://aka.ms/wiqd/install.sh | bash

    See Installation for flags, troubleshooting, and manual steps.

  2. Install the Copilot CLI plugin from its marketplace if you want guided authoring. The WIQD installer does not manage host plugins:

    Terminal window
    copilot plugin install wiqd@awesome-copilot

    Or use the WIQD marketplace:

    Terminal window
    copilot plugin marketplace add microsoft/wiqd
    copilot plugin install wiqd@wiqd

    For Claude Code, add microsoft/wiqd and install wiqd@wiqd. Start a new host session.

  3. Sign in — provisioning and sharing a plugin touch your Microsoft 365 tenant:

    Terminal window
    wiqd auth login --interactive

    See Authentication for how sign-in works across providers.

  4. Verify your environment:

    Terminal window
    wiqd doctor

With that done, you have two paths to the same result. Start with Path 1 — it’s the fastest way to build a plugin and it teaches you the lifecycle as you go. Reach for Path 2 when you need scripting, CI, or exact reproducibility.

Once the wiqd plugin is installed, GitHub Copilot CLI carries a plugin-authoring orchestrator: you describe what you want in natural language, and it drives the wiqd CLI on your behalf — including the full wiqd plugin ... surface — while orienting you on the underlying Build → Improve → Preview → Publish agent lifecycle. You never have to remember a flag; you just say what you want next.

Saying any of these in GitHub Copilot CLI routes the conversation into the plugin-authoring workflow:

create a standalone plugin · build a plugin with a skill · build a plugin with a connector · add a skill to my plugin · add a connector to my plugin · add an agent to my plugin · validate my plugin · package my plugin · share my plugin · show my plugin · list my plugins · import an open plugin · import a claude plugin · export my plugin for claude · export my plugin for cursor · delete my plugin

You say: “Create a standalone plugin called Triage Helper.” wiqd does: runs wiqd plugin create --name "Triage Helper", cds into the new project, and reports back — then suggests the logical next moves: add a skill, add an MCP connector, or add a declarative agent.

You say: “Add a skill for triaging incoming issues.” wiqd does: runs wiqd plugin add skill --name "Triage Issues", scaffolding appPackage/skills/triage-issues/SKILL.md and registering it in the manifest. It then suggests adding another capability, or validating what you have so far.

You say: “Validate my plugin.” wiqd does: runs wiqd plugin validate (the offline static check). On a clean result, it suggests provisioning the plugin or exporting it for another tool. There is no shipped command that merges a whole plugin into an existing agent. For an OpenAPI custom connector, it omits export because that format cannot represent the ConnectionVault reference, and explains that static validation does not establish packaging or deployment readiness.

You say: “Provision it, then package and share it with my team.” wiqd does: runs wiqd plugin provision, then wiqd plugin package now that the environment file it needs exists, then wiqd plugin share --scope users --email <you supply>. Each step’s report tells you what ran and what’s next, so a multi-step request like this one still gives you a checkpoint after every command.

This mirrors the same create → add → validate → provision → package → share lifecycle you’d run by hand — the orchestrator just chooses and sequences the commands for you, one reported step at a time.

Reach for the CLI directly instead of the conversational path when you’re scripting a CI/CD pipeline, need fully deterministic and reproducible non-interactive automation, or you simply prefer typing commands yourself. Every command below is the same one the conversational path runs under the hood — nothing here is a different surface, just a different way to drive it.

Terminal window
# 1. Scaffold the plugin container
wiqd plugin create --name my-plugin
cd my-plugin
# 2. Compose whatever capabilities you need, in any combination
wiqd plugin add agent
wiqd plugin add skill --name "Triage Issues"
wiqd plugin add connector --name "Contoso Tools" \
--description "Contoso toolbelt over MCP" --url https://tools.contoso.com/mcp
# Or reuse a connector the Microsoft 365 catalog already publishes — the catalog
# supplies its URL and credential reference. Copy the selector from `connector list`.
wiqd connector list --search "Azure DevOps"
wiqd plugin add connector --selector "Azure DevOps MCP"
# Or reference an existing custom connector by its logical name.
# --dev-preview explicitly updates both manifestVersion and $schema.
wiqd plugin add connector --kind openapi --name "Contoso Kusto" \
--description "Queries the existing Contoso Kusto connector" \
--auth-reference-id shared_kusto --dev-preview
# 3. Validate offline before you provision or package
wiqd plugin validate
# 4. Provision first — it writes the env file package/share both depend on
wiqd plugin provision
# 5. Build the deployable .zip
wiqd plugin package
# 6. Run the full package-first deep validation against AVL
wiqd plugin validate --mode deep
# 7. Share it
wiqd plugin share --scope users --email you@contoso.com
# …or share with your whole tenant:
wiqd plugin share --scope tenant
# Inspect at any point
wiqd plugin show
wiqd plugin list --root .
# Tear down what provision created (--yes skips the confirmation prompt for non-interactive use)
wiqd plugin delete --env local --yes

For every flag on every one of these commands, see wiqd plugin in the CLI reference rather than duplicating the full flag set here.

--kind openapi does not author an OpenAPI API-plugin action and does not create a custom connector. It writes a ConnectionVault pointer to an existing custom connector. The --auth-reference-id value is its logical name (for example shared_kusto), not a URL, secret, connection instance, or OAuth registration id. This connector shape is currently available only through explicit devPreview authoring; wiqd does not assume a tentative numbered manifest version supports it.

Already have a plugin authored for another host, or want to hand yours to one? Two commands bracket the lifecycle instead of requiring a hand-rolled conversion script:

Terminal window
# Bring a foreign-format plugin into wiqd instead of starting from `create`
wiqd plugin import --path ./my-open-plugin \
--privacy-url https://contoso.com/privacy \
--terms-url https://contoso.com/terms
# Hand a wiqd plugin project to another host
wiqd plugin export --format claude-plugin

wiqd plugin import recognizes an Open Plugin, Claude plugin, or Cursor plugin source and produces a new wiqd plugin project from it. --privacy-url/--terms-url are only required on a plugin’s first import — if the source was produced by a prior wiqd plugin export, the round-trip metadata already carries them. wiqd plugin export does the inverse, defaulting to --format open-plugin (also accepts claude-plugin and cursor-plugin), writing an uncompressed directory under <path>/export/<format>. The output layout uses the core backend: writes the selected format layout; FxCore always writes plugin.json at the export root and uses --format only for the default output directory and reported format.

The imported project is ready for the full lifecycle. The TeamsFx importer creates its deployment environment (currently dev), and wiqd binds the package manifest to that environment’s TEAMS_APP_ID. For a default import with no app ID, continue with wiqd plugin validate, then run wiqd plugin provision --env dev before wiqd plugin package --env dev, wiqd plugin publish --env dev, wiqd plugin share --env dev, or package-first deep validation. Packaging through either backend fails closed while the selected environment file is missing or its TEAMS_APP_ID is empty, unresolved, or malformed, instead of allowing a random package-only ID.

Mock import follows the same finalized project contract: the package manifest keeps the ${{TEAMS_APP_ID}} binding, the generated dev environment is present, and an explicit --app-id is written to that environment before provision/package checks run.

If you pass --app-id, or a supported source extension carries a real GUID, import writes that ID into .env.dev; the project is already identity-bound for packaging. A wiqd export does not invent or preserve an unresolved ${{TEAMS_APP_ID}} token as a round-trip ID, so only a real GUID from a supported producer qualifies. Provision/update remains the normal tenant lifecycle step before sharing a newly imported app.

Rather than assembling a package and hoping upload-time validation passes, validate continuously as you build:

  • Static (default) — wiqd plugin validate runs the offline MVL (Manifest Validation Library) engine over the declarative-agent surface only: declarativeAgent.json plus any referenced API-plugin manifest or OpenAPI specs. It does not inspect the top-level Teams manifest or the content of agentSkills[]/SKILL.md files — so a skill-only or connector-only plugin passes static validation vacuously. This is expected, not a gap: it’s an inner-loop check, not full coverage.
  • Deep (--mode deep) — package-first: after wiqd plugin package builds a .zip, wiqd plugin validate --mode deep validates that built package against AVL (App Validation Library) through the Teams Developer Portal, via deep validation. This is where manifest-schema rejections and skill-content issues actually surface. See the Plugin authoring reference for the full validation-code tables and how they map to static vs. deep.

Static validation does not inspect root agentConnectors[], including openApiConnector. For a preview custom connector, package before deep validation. The bundled fx-core 3.1.5-alpha.36e30da2f6.0 supports local devPreview packaging of OpenAPI and remote MCP connectors using ConnectionVault, preserving their fields and leaving the source manifest unchanged. Local packaging success is not a server verdict. Check the built ZIP with wiqd plugin validate --mode deep --package-file <zip>. Report the returned findings rather than assuming every rejection is a rollout issue. Runtime invocation and Store/Partner Center acceptance remain separate checks. wiqd plugin export also fails explicitly for this connector kind because Open Plugin has no verified representation for the ConnectionVault logical reference.

wiqd carries you through build → validate → provision → package → publish or share. Choose the tenant path based on how users should receive the plugin:

  • Organization app catalog — wiqd plugin publish --env <env> submits the provisioned plugin to the tenant’s org admin catalog. Submission succeeds before approval; tenant users can discover the plugin only after an administrator approves it.
  • Direct access — wiqd plugin share --scope users --email ... grants access to named users, or wiqd plugin share --scope tenant grants tenant-wide access. Sharing is a different operation from catalog submission.
  • Public store — run wiqd plugin package --env <env> and submit the .zip through Partner Center. wiqd does not automate Partner Center certification or public-store submission.

Normal publish validates the effective app manifest that it sends to fx-core: appPackage/manifest.json by default, or the contained file selected with --manifest. All DA manifests, skill folders, Worker references, and other package files resolve from that selected manifest’s directory. A valid override is not blocked by stale or invalid DA references in the unused default manifest.

Before catalog submission, you can run a no-submit readiness check:

Terminal window
# Fully offline: project/path checks plus the applicable static DA phase
wiqd plugin publish --dry-run --no-runtime --no-network
# Full applicable validation for a plugin that contains a declarative agent
wiqd plugin publish --dry-run --env dev

Declarative-agent checks are explicitly skipped for skill/connector-only plugins; a skip does not claim those components received full runtime validation.

Where wiqd stops: wiqd automates submission to the tenant organization app catalog, but tenant administrator approval remains an admin action. Partner Center certification and public-store publication also remain outside wiqd.

  • What is a plugin? — the mental model and the three meanings of “skill”
  • wiqd plugin — the full command reference
  • Plugin authoring reference — package anatomy, SKILL.md rules, connector requirements, validation codes, and the section-by-section Learn parity table
  • Quickstart — the equivalent 5-minute walkthrough for a plain wiqd agent project