Skip to main content

Developer Hub

Quickstart

A focused guide for the quickstart stage of extension development. coc.nvim extensions expose an activate(context) entry point to the Node.js extension host.

Extension quickstart

The recommended path is to let an Agent Skills-compatible coding agent scaffold and verify the project with coc-create. A manual setup is included below when you need full control.

Recommended · AI-assisted

Create the project with coc-create

The coc-create skill generates a TypeScript extension with a sample command, strict type checking, an esbuild production build, coc-test, and CI coverage for both Vim and Neovim.

1. Check the requirements

  • Node.js 22.15 or newer
  • Vim and Neovim for the two editor test lanes
  • Codex or another Agent Skills-compatible coding agent

2. Install coc-create for Codex

Terminal
1npx skills@latest add neoclide/coc-skills \
2 --skill coc-create \
3 --agent codex \
4 --global \
5 --yes

Start a new Codex task after installation so the agent can discover the newly installed skill.

3. Describe the extension to the agent

Prompt
1$coc-create
2 
3Create a new coc.nvim extension in ~/vim-dev/coc-word-count.
4 
5Package name: coc-word-count
6Description: Show the word count of the current buffer
7Initial command: word-count.show
8Behavior: Display the word count of the current buffer
9License: MIT
10Author: Your Name
11 
12Install the dependencies and verify the extension with type checking,
13a production build, and coc-test using both Neovim and Vim.

What gets verified

Type checking, the production build, both coc-test editor lanes, and npm pack --dry-run.

Explicit project boundaries

Use an empty target directory. Git initialization, commits, pushes, and npm publication remain explicit developer decisions.

coc-create source and instructionsInspect the skill, deterministic scaffolder, templates, and validation workflow.

Manual setup

Create the package files yourself when an AI-assisted scaffold is not appropriate.

01

Create an empty package

02

Install the SDK

03

Export activate()

04

Build and test

Terminal
1mkdir coc-demo && cd coc-demo
2npm init -y
3npm install --save-dev coc.nvim@next coc-test 'typescript@^6' 'esbuild@^0.28' '@types/node@^22'
4mkdir -p src test
5# Copy the package.json, tsconfig.json, esbuild.mjs, src/index.ts, and
6# test/extension.test.ts shown below, then run:
7npm run typecheck
8npm run build
9npm run test:nvim
10npm run test:vim
package.json
1{
2 "name": "coc-demo",
3 "version": "0.1.0",
4 "description": "A coc.nvim extension example",
5 "main": "lib/index.js",
6 "files": ["lib"],
7 "engines": { "node": ">=22.15.0", "coc": "^0.0.82" },
8 "activationEvents": ["onCommand:coc-demo.hello"],
9 "contributes": { "commands": [{ "command": "coc-demo.hello", "title": "Say hello" }] },
10 "scripts": {
11 "build": "node esbuild.mjs",
12 "prepare": "npm run build",
13 "typecheck": "tsc -p tsconfig.json --noEmit",
14 "test": "npm run test:nvim",
15 "test:nvim": "coc-test --nvim "test/**/*.test.ts"",
16 "test:vim": "coc-test --vim "test/**/*.test.ts""
17 },
18 "coc-test": { "entryFile": "src/index.ts" }
19}
tsconfig.json
1{
2 "compilerOptions": {
3 "target": "ES2022",
4 "module": "ES2022",
5 "moduleResolution": "Bundler",
6 "lib": ["ES2022"],
7 "strict": true,
8 "noEmit": true,
9 "allowImportingTsExtensions": true,
10 "esModuleInterop": true,
11 "skipLibCheck": true,
12 "forceConsistentCasingInFileNames": true
13 },
14 "include": ["src/**/*.ts", "test/**/*.ts"]
15}
esbuild.mjs
1import { build } from 'esbuild'
2 
3await build({
4 entryPoints: ['src/index.ts'],
5 bundle: true,
6 external: ['coc.nvim'],
7 format: 'cjs',
8 platform: 'node',
9 target: 'node22',
10 outfile: 'lib/index.js',
11})
src/index.ts
1import { commands, ExtensionContext, window } from 'coc.nvim'
2 
3export const commandId = 'coc-demo.hello'
4 
5export function activate(context: ExtensionContext): void {
6 context.subscriptions.push(commands.registerCommand(commandId, async (name?: string) => {
7 const message = `Hello from ${name ?? 'coc.nvim'}!`
8 await window.showInformationMessage(message)
9 return message
10 })
11}
test/extension.test.ts
1import assert from 'node:assert/strict'
2import { beforeEach, describe, it } from 'node:test'
3import { commands, workspace } from 'coc.nvim'
4import { commandId } from '../src/index.ts'
5 
6beforeEach(async () => {
7 await workspace.nvim.command('enew!')
8})
9 
10describe('coc-demo', () => {
11 it('activates and registers its command', () => {
12 assert.equal(commands.has(commandId), true)
13 })
14 
15 it('executes through the editor-backed coc.nvim runtime', async () => {
16 const message = await commands.executeCommand(commandId, 'Vim')
17 assert.equal(message, 'Hello from Vim!')
18 assert.equal(await workspace.nvim.eval('bufexists(bufnr())'), 1)
19 })
20})
Local smoke test
1# From the extension project after npm run build
2# Use either editor; the runtimepath method is supported by coc.nvim.
3vim -c 'set runtimepath^=/absolute/path/to/coc-demo' -c 'edit demo.txt'
4# Or: nvim -c 'set runtimepath^=/absolute/path/to/coc-demo' -c 'edit demo.txt'
5 
6# In the opened Vim/Neovim session, invoke the contributed command:
7:CocCommand coc-demo.hello
The supported source-loading path is Vim's runtimepath: install dependencies, build lib/index.js, then prepend the project root. Use :CocInstall only for a published npm or GitHub package; the runtimepath method keeps local iteration separate from an npm release.

Continue with the typings reference

Use the generated project as a base, then find the current commands, workspace, language, window, and extension APIs for the feature you want to build.

Browse typings
Compatibility note: coc.nvim follows its own extension API. Some VS Code concepts have similar names, but behavior, lifecycle, editor UI, and supported fields must be checked in the typings reference rather than assumed equivalent.