@lancedb/lancedb) runs inside it like any other npm package. There’s no database server to
start: the extension opens a table on local disk and queries it in-process. That makes LanceDB
a good fit for features such as semantic search over a workspace, retrieval for an AI assistant,
or a cache of embeddings that survives restarts.
The extension host differs from a plain Node.js script in three ways that trip people up:
- Where you can write. Relative paths resolve against the extension host’s working directory, not the workspace, and that directory may be read-only.
- Bundling. The default extension template bundles everything with esbuild, which can’t
bundle LanceDB’s native
.nodebinary. - Packaging. LanceDB installs a native binary for the current platform only, so a VSIX built on one OS doesn’t run on another.
What you’ll build
- LanceDB: Index Markdown in Workspace splits every
*.mdfile into paragraphs and stores them in a LanceDB table, with embeddings fromXenova/all-MiniLM-L6-v2run locally through Transformers.js. - LanceDB: Search Markdown embeds your query, shows the five closest paragraphs, and opens the one you pick.
Create the extension
1
Scaffold a TypeScript extension
Use the official generator. Choose New Extension (TypeScript) and esbuild as the bundler.
2
Install LanceDB
apache-arrow as a dependency, pinned inside
LanceDB’s peer dependency range (>=15.0.0 <=18.1.0). If the project already depends on a
newer apache-arrow, installing LanceDB fails with ERESOLVE.LanceDB also needs @types/node 22 or later. The generator’s template already uses a recent
version; an old one causes Cannot find module 'node:stream' errors.You don’t need to install @huggingface/transformers yourself. LanceDB pins it as an optional
dependency, and installing a different version gives your code a copy that LanceDB doesn’t use.3
Skip type checking of dependencies
The type declarations of
apache-arrow and the ONNX runtime used by Transformers.js refer to
browser types such as ReadableStreamReadResult and HTMLCanvasElement. The template’s
tsconfig.json doesn’t include the DOM library, so tsc reports errors inside node_modules.
Turn on skipLibCheck:tsconfig.json
Store the database in extension storage
Give LanceDB an absolute path inside the storage VS Code reserves for your extension:context.storageUri: a folder for the current workspace. It’sundefinedwhen no folder is open. Use it for anything derived from the workspace, such as this index.context.globalStorageUri: a folder shared by all workspaces. Use it for things like downloaded models.
lancedb.connect("data"). It resolves against the extension
host’s working directory, which isn’t the workspace and may be read-only: this causes the
Read-only file system (os error 30) error on macOS.
Put the LanceDB code in its own module, src/search.ts:
src/search.ts
src/search.ts
Index and search
Split each file into paragraphs, and keep the line each one starts on so a result can open the file at the right place:src/search.ts
huggingface embedding function.
Because the text column is declared as the function’s source field, LanceDB computes the
vector column for you as the rows are written:
src/search.ts
src/search.ts
mode: "overwrite" rebuilds the index each time, which is fine for a few thousand paragraphs.
For larger workspaces, update only the files that changed with
mergeInsert.
Wire up the commands
Declare the two commands inpackage.json:
package.json
src/extension.ts:
src/extension.ts
- The index goes in
storageUri, so each workspace gets its own table. - The embedding model (about 90 MB) downloads on first use. By default Transformers.js caches it
inside
node_modulesin the installed extension, which is replaced on every update. Settingenv.cacheDirkeeps it in global storage. Load the module withimport(), notrequire(): LanceDB loads the ES module build of@huggingface/transformers, andrequire()returns a separate copy whose settings LanceDB never sees. - Each paragraph is stored with its file’s URI, not a path relative to a workspace folder. In a
multi-root workspace,
findFilessearches every folder and two folders can hold the same relative path, so only the URI opens the right file.asRelativePathturns it back into a short label, which names the folder when there’s more than one. - When another extension or a test passes a query to
lancedbSearch.search, it returns the rows instead of opening a picker. That’s how the results below were produced, with each URI shown throughasRelativePath.
Sample results
Here are the results of running the extension in VS Code against a small workspace of six Markdown files that document a fictional CLI. The columns are the vector distance (lower is closer), the file and line, and the paragraph.Bundle with esbuild
The template bundles your code and its dependencies intodist/extension.js. esbuild can’t
bundle native binaries, so building with LanceDB fails with
No loader is configured for ".node" files. Mark the packages that load native code as
external so they’re loaded from node_modules at runtime:
.vscodeignore excludes
node_modules/**. Delete that line. vsce then includes your production dependencies and
leaves out the dev dependencies. If you skip this step, the packaged extension fails to
activate with Cannot find module '@lancedb/lancedb', and its commands report
command 'lancedbSearch.index' not found.
Package a VSIX for each platform
npm installs LanceDB’s native binary only for the machine it runs on, such as@lancedb/lancedb-win32-x64-msvc on 64-bit Windows. Build one VSIX per platform, each on a
matching machine or CI runner, and publish them all. The Marketplace serves each user the right one.
LanceDB doesn’t publish a binary for Intel Macs (
darwin-x64).
The table covers LanceDB’s own binaries. This sample’s local model also needs
onnxruntime-node, which Transformers.js loads when the extension activates. Its Linux binaries
link against glibc and it publishes no musl build, so the sample as written doesn’t run on
Alpine, whatever --target you pass. To ship the alpine-* targets, embed with a hosted API or
use full-text search instead, or build ONNX Runtime for musl yourself.