Plugins

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the Python Reference bibliography.

Poetry loads every installed plugin package when it starts. Plugins add commands (poetry export and poetry shell are plugins since Poetry 2.0) or change how Poetry behaves. This page covers using and writing them with Poetry 2.5.1; the examples ran on 2026-10-22 on Linux with CPython 3.14.6 and the official installer’s Poetry, with scratch paths shortened to …. pip has no plugin system.

Installing a plugin into Poetry

Plugins live in Poetry’s own environment, not in the project’s. Use the installation method you installed Poetry with:

Poetry installed with Add Remove

official installer (or any)

poetry self add poetry-plugin-shell

poetry self remove poetry-plugin-shell

pipx

pipx inject poetry poetry-plugin-shell

pipx uninject poetry poetry-plugin-shell

pip into a venv

$POETRY_HOME/bin/pip install poetry-plugin-shell

$POETRY_HOME/bin/pip uninstall poetry-plugin-shell

self add checks that the plugin is compatible with the running Poetry before installing it and accepts the same specifications as poetry add (constraints, git, path). The documentation warns that self add and self remove can be problematic on Windows, where the other methods are preferred.

poetry self add poetry-plugin-shell
Using version ^1.0.1 for poetry-plugin-shell

Updating dependencies
Resolving dependencies...

Package operations: 3 installs, 0 updates, 0 removals

  - Installing ptyprocess (0.7.0)
  - Installing pexpect (4.9.0)
  - Installing poetry-plugin-shell (1.0.1)

Writing lock file

poetry self remove poetry-plugin-shell removes it and the dependencies it brought (3 removals). The self commands keep their own pyproject.toml and poetry.lock for Poetry’s environment, so plugins survive poetry self update; see Installation & Self-Management.

poetry self show plugins lists what is loaded:

  - poetry-plugin-export (1.10.1) Poetry plugin to export the dependencies to various formats
      1 application plugin

      Dependencies
        - poetry (>=2.1.0,<3.0.0)
        - poetry-core (>=2.1.0,<3.0.0)
        - tomlkit (>=0.11.4,<1.0.0)

Plugins a project requires (2.0)

A project can declare the plugins it needs. poetry install then installs any that Poetry’s environment lacks for this project only, under .poetry/plugins in the project directory (add .poetry/ to .gitignore):

[project]
name = "app"
version = "0.3.0"
requires-python = ">=3.10"
dependencies = ["attrs (>=25)"]

[tool.poetry]
package-mode = false

[tool.poetry.requires-plugins]
tally-plugin = {path = "../tally-plugin", develop = true}
poetry install
poetry tally-info
Creating virtualenv app in …/app/.venv
Ensuring that the Poetry plugins required by the project are available...
The following Poetry plugins are required by the project but are not installed in Poetry's environment:
  - tally-plugin (*) @ file:///…/tally-plugin
Installing Poetry plugins only for the current project...
Updating dependencies
Resolving dependencies...

Package operations: 1 install, 0 updates, 0 removals

  - Installing tally-plugin (0.1.0 …/tally-plugin)

Writing lock file

Updating dependencies
Resolving dependencies...

Package operations: 1 install, 0 updates, 0 removals

  - Installing attrs (26.1.0)

Writing lock file
app 0.3.0: 1 main dependencies

The entries take the same forms as dependencies (">1.0", a table with path and develop = true for plugin development). Before the install, and with --no-plugins, the command does not exist:

The requested command tally-info does not exist.

Documentation: https://python-poetry.org/docs/cli/

A project plugin can even replace the version in Poetry’s environment, but installation fails if its dependencies conflict with Poetry’s own.

Official plugins

Plugin What it adds

poetry-plugin-export

poetry export to requirements.txt, constraints.txt and pylock.toml. Bundled with Poetry until 1.8, separate since 2.0; see Exporting Requirements & Bundling.

poetry-plugin-shell (1.0.1)

poetry shell, which spawns a subshell in the environment (leave it with exit); moved out of Poetry in 2.0 in favour of poetry env activate. Its README says the project is looking for maintainers.

poetry-plugin-bundle

poetry bundle venv, which installs the project and its locked dependencies into a standalone virtual environment.

Writing a plugin

A plugin is an ordinary package that depends on poetry and declares an entry point:

  • poetry.plugin: a class implementing poetry.plugins.plugin.Plugin, whose activate(poetry, io) receives the Poetry object (project, package, configuration) and can change it;

  • poetry.application.plugin: a class implementing poetry.plugins.application_plugin.ApplicationPlugin, whose activate(application) receives the console application and can register commands or event listeners (Cleo’s COMMAND, SIGNAL, TERMINATE and ERROR events).

The tally-plugin used above, in full:

[project]
name = "tally-plugin"
version = "0.1.0"
description = "Adds a 'poetry tally-info' command."
requires-python = ">=3.10"
dependencies = ["poetry (>=2.0,<3.0)"]

[project.entry-points."poetry.application.plugin"]
tally-info = "tally_plugin.plugin:TallyPlugin"

[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
# src/tally_plugin/plugin.py
from cleo.commands.command import Command
from cleo.events.console_command_event import ConsoleCommandEvent
from cleo.events.console_events import COMMAND
from cleo.events.event_dispatcher import EventDispatcher
from poetry.console.commands.install import InstallCommand
from poetry.plugins.application_plugin import ApplicationPlugin


class TallyInfoCommand(Command):
    name = "tally-info"
    description = "Prints the project's name, version and dependency count."

    def handle(self) -> int:
        poetry = self.application.poetry
        package = poetry.package
        self.line(f"{package.name} {package.version}: "
                  f"{len(package.requires)} main dependencies")
        return 0


def factory() -> Command:
    return TallyInfoCommand()


class TallyPlugin(ApplicationPlugin):
    def activate(self, application) -> None:
        application.command_loader.register_factory("tally-info", factory)
        application.event_dispatcher.add_listener(COMMAND, self.report_lock)

    def report_lock(
        self,
        event: ConsoleCommandEvent,
        event_name: str,
        dispatcher: EventDispatcher,
    ) -> None:
        if not isinstance(event.command, InstallCommand):
            return
        locker = event.command.poetry.locker
        if locker.is_locked():
            count = len(locker.lock_data.get("package", []))
            event.io.write_line(f"tally-plugin: poetry.lock pins {count} packages")

Because the project requires it with develop = true, editing the plugin takes effect at once (editable project plugins arrived in 2.3). The listener runs before every install (and sync, a subclass of it):

poetry install
poetry tally-info
tally-plugin: poetry.lock pins 1 packages
Installing dependencies from lock file

No dependencies to install or update
app 0.3.0: 1 main dependencies

Rules from the documentation:

  • register commands through command_loader.register_factory rather than application.add(…), so the command is only imported when it is used and Poetry stays fast;

  • a plugin must not remove or change Poetry’s core commands;

  • there is no stable public API: plugins use Poetry internals, which can change between releases, so test against the latest release, the release branch and main on a schedule, and keep the poetry constraint tight. Poetry 2.3 deprecated poetry.utils._compat.metadata, which plugins often imported; use importlib.metadata.