Pi extensions: where they live and what they can reach
What a Pi coding agent extension is, where Pi loads extensions from, installing packages with pi install, and the keys and files an extension can reach.
A Pi extension is a TypeScript module that Pi loads into its own process to add tools the model can call, / commands, shortcuts, event hooks, model providers, MCP servers or screen elements. Pi finds your own in ~/.pi/agent/extensions/ and a project’s in .pi/extensions/, and installs shared ones as packages with pi install npm:<package>, which puts them in ~/.pi/agent/npm/node_modules/. An extension runs with your account’s full permissions and can read the model keys Pi holds; Pi doesn’t sandbox extensions, its maintainers said in June 2026 that they don’t scan the packages its gallery lists, and as of version 1.3.0 installing a package also runs that package’s npm install scripts. Read a package’s source before you install it, and run Pi inside a container or virtual machine if you want a real boundary.
Extensions beside Pi’s other add-ons
Pi has four kinds of add-on, and only one of them runs code:
| Kind | What it is | Runs code |
|---|---|---|
Extension | A TypeScript or JavaScript module loaded into Pi | Yes, inside Pi |
Skill | Instructions and files in the open Agent Skills format, loaded when a task calls for them | Only what the model then chooses to run |
Prompt template | A Markdown file that becomes a / command | No |
Theme | Colours for the terminal interface and HTML exports | No |
A package is the way any of these is shared: Pi’s packages page says packages “install and distribute extensions, skills, prompt templates, and themes as one unit,” and a package is an ordinary npm package or folder. Pi’s extensions page draws the line between the first two: use an extension “when a workflow needs tools, commands, event handlers, model providers, session state, or terminal UI rather than instructions alone.”
What an extension can add
An extension exports one function that receives Pi’s extension API and registers what it adds. Pi loads it with jiti, so there is no build step. Among what it can register:
- a tool the model can call, including one with the same name as a built-in tool, which replaces it (Pi’s own example names logging, access control and sandboxing as reasons);
- a command typed after
/, a keyboard shortcut or a command-line flag; - event handlers for sessions, messages, provider requests and tool calls; a
tool_callhandler “can mutate input or block execution,” which is how every permission extension works; - a model provider, an MCP server, or a rule that routes each request to a model;
- interface: status lines, dialogs and custom rendering in the terminal.
Pi also ships four extensions of its own, loaded by default: builtin:mcp, builtin:llama.cpp, builtin:codemode and builtin:tool-search. MCP support has been one since version 0.99 (September 29, 2026), and an installed extension that registers /mcp, such as pi-mcp-adapter, replaces it.
Where extensions live
| Place | Loaded |
|---|---|
~/.pi/agent/extensions/ | Always, for every project |
.pi/extensions/ in a project | Only after you trust that project |
Paths listed under extensions in settings.json | With the settings file they are in |
pi -e ./my-extension.ts | For that one run |
In those folders Pi loads a .ts or .js file directly, a subfolder with an index.ts or index.js, or a subfolder whose package.json has a pi field, and goes no deeper than one level. An extension you drop in by hand brings no dependencies with it: Pi’s docs say to put them in a package.json beside it and run npm install there. /reload picks up changes without restarting. PI_CODING_AGENT_DIR moves ~/.pi/agent elsewhere.
When two extensions add the same tool, the first in Pi’s order keeps it: a project’s before yours, yours before a package’s. Pi reports the clash as Failed to load extension "…": Tool "web_search" conflicts with …, though both stay loaded; only the first one’s tool is used. Two web-search packages installed side by side is the common way to meet it.
Installing a package
pi install npm:@example/pi-tools # from npm
pi install npm:@example/pi-tools@1.0.0 # pinned to a version
pi install git:github.com/example/pi-tools # from git (https:// and ssh:// work too)
pi install ./my-package # a folder on disk, used where it is
pi install -l npm:@example/pi-tools # for this project only
pi -e npm:@example/pi-tools # one run, not added to settings
pi list # what is installed, and where
pi update --extensions # update installed packages (pi update alone updates Pi)
pi remove npm:@example/pi-toolsThere is no confirmation step: pi install installs and prints Installed <source>. Where the package lands:
- From npm, in
~/.pi/agent/npm/node_modules/<package>(since version 0.75.0; before that, npm’s global folder), with its dependencies. - From git, cloned to
~/.pi/agent/git/<host>/<path>. - For a project (
-l), under the project’s.pi/npm/or.pi/git/, which Pi keeps out of git with its own.gitignore. What travels with the repository is the line in.pi/settings.jsonnaming the package.
The record of what you installed is the packages list in ~/.pi/agent/settings.json. A version you name is pinned, and so are git tags and commits; an unpinned npm package moves with pi update --extensions. pi config turns individual resources from a package, or Pi’s built-in extensions, on and off.
The gallery at pi.dev/packages lists every npm package carrying the pi-package keyword: 5,437 of them on October 20, 2026, extensions, skills, prompt templates and themes together. Its download counts are npm’s. A package is listed by carrying the keyword; each entry has a link to report a malicious one, and one of Pi’s maintainers wrote in June 2026: “We don’t scan extensions proactively today.”
What an extension can reach
Everything you can. Pi’s own warning: an extension “runs inside the Pi process with the same operating-system permissions. It can inspect prompts, tool calls, files, credentials, and session history, so load extensions only from sources you trust.” In particular:
- Your model keys. Pi keeps saved API keys and sign-ins in
~/.pi/agent/auth.json, and the extension API hands an extension the key for any provider on request. An extension can also rewrite the payload and headers of each request Pi sends to a model provider. - The rest of your account: your files, environment variables, SSH keys, the network, and every tool your user can run. Pi’s security page: “Extensions, package installers, language servers, and other child processes run with those same permissions unless an operating-system or virtualization boundary restricts them.”
- Install time, before Pi loads anything. In version 1.3.0,
pi installrunsnpm installwithout--ignore-scripts, so a package’s install scripts, and its dependencies’, run as you the moment you install it. Pi’s own install instructions use--ignore-scriptsfor Pi itself; its package installer does not. - Packages that install themselves. A package listed in your
settings.jsonbut missing from disk is installed at the next start, with no prompt: copy your Pi settings to a new machine and its packages follow. A project’s listed packages do the same once you trust the project.
This is by design, not an oversight. Pi’s security policy says “the Pi coding agent intentionally does not have a sandbox” and lists “Behavior of pi extensions or skills installed by the user” and “Risks from installing untrusted extensions, skills, packages, or tools” as outside what it treats as a vulnerability.
Project trust is the one check. Pi asks before loading a folder’s .pi/settings.json, .pi/extensions, skills, prompts and the rest, which since version 0.79.0 (June 2026) stops a cloned repository from running its own extensions the moment you open it; before that, it did (CVE-2026-54325). Pi’s page is plain about the limit: “Project trust does not limit what tool calls can access or affect.” And AGENTS.md and CLAUDE.md load whether or not you trust the folder.
Extensions on a server, with nobody watching
Extensions load in every mode: the interactive terminal, RPC, JSON and print (pi -p). Their interface does not. In print and JSON modes there is nobody to ask, so a confirmation dialog answers no: an extension that asks before running rm -rf blocks it instead, which is the safe way round but means an unattended run stops short wherever a gate would have asked.
- A project’s extensions are skipped in a scripted run unless something decides trust for it. With the default setting (
defaultProjectTrust: "ask"), print, JSON and RPC runs skip them;--approveloads them for that run, and"always"loads them every time. Your own extensions and packages load either way. - Missing packages download at start-up, in any mode, unless the run is offline (
--offlineorPI_OFFLINE=1). - There are still no permission prompts. Pi’s home page lists “No permission popups” among what it didn’t build, and points at containers or an extension of your own. The gallery has permission extensions (
@gotgenes/pi-permission-systemfor allow, ask and deny rules;cc-safety-netfor blocking destructive commands and reads of key files), and Pi ships examples: a permission gate for dangerous shell commands, protected paths for its write and edit tools, and a sandbox that wraps the shell tool with bubblewrap on Linux. Each runs inside Pi, so it limits what the model’s tool calls do, not what another extension does.
For a real boundary, Pi’s containerization page is clear about where it sits: when the whole Pi process runs in an isolated environment, “its extensions run there too.” Two catches from the same page: mounting ~/.pi/agent into the container “exposes your Pi credentials, settings, extensions, and sessions,” and isolating only the built-in tools, as the Gondolin example does, leaves other extensions’ tools on the host. On a server, running Pi as a user account of its own is a simpler boundary of the same kind: its extensions reach that account’s files and keys, and not yours.
Before you install someone else’s
- Read the source, the extension’s own code and the
scriptsin itspackage.json. Pi’s advice is the same: “Review third-party package source before installing it.” - Check the npm name against the repository it claims. A copy of a popular package under another name has been reported on Pi’s tracker.
- Pin a version (
npm:package@1.2.3), so an update you didn’t read never arrives on its own. - Remember
-eruns the code too. Trying a package for one run is not a preview: it loads and runs it like any other. - Turn off what you don’t use with
pi config, orpi removeit.pi --no-extensions(-ne) starts a run with none at all, built-ins included.
What the Pi coding agent is covers installing Pi and choosing where to run it, and Pi against OpenCode how its no-prompts design compares.