Plugin How-to Guides

Plugin hook lifecycle

The diagram below shows when each plugin hook fires during a tox run:

        flowchart TD
    start(( )) --> register[tox_register_tox_env]
    register --> add_option[tox_add_option]
    add_option --> extend[tox_extend_envs]
    extend --> core_config[tox_add_core_config]
    core_config --> env_config[tox_add_env_config]

    env_config --> envloop

    subgraph envloop [for each environment]
        direction TB
        on_install[tox_on_install]
        on_install --> before[tox_before_run_commands]
        before --> cmds[run commands]
        cmds --> after[tox_after_run_commands]
        after --> teardown[tox_env_teardown]
    end

    teardown --> done(( ))

    classDef setupStyle fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e3a5f
    classDef envStyle fill:#dcfce7,stroke:#22c55e,stroke-width:2px,color:#14532d
    classDef cmdStyle fill:#ede9fe,stroke:#8b5cf6,stroke-width:2px,color:#3b0764
    classDef installStyle fill:#ffedd5,stroke:#f97316,stroke-width:2px,color:#7c2d12

    class register,add_option,extend,core_config,env_config setupStyle
    class before,after,teardown envStyle
    class cmds cmdStyle
    class on_install installStyle
    

Add custom config to an environment

Use tox_add_env_config to register new configuration keys on every tox environment. Users can then set these keys in tox.toml or tox.ini like any built-in setting.

from tox.config.sets import EnvConfigSet
from tox.plugin import impl
from tox.session.state import State


@impl
def tox_add_env_config(env_conf: EnvConfigSet, state: State) -> None:
    env_conf.add_config(
        keys=["my_timeout"],
        of_type=int,
        default=30,
        desc="timeout in seconds for custom checks",
    )

Access the value later via env_conf["my_timeout"].

Run code before or after commands

Use tox_before_run_commands and tox_after_run_commands to execute logic around the commands phase. The tox_after_run_commands hook receives the exit code and the list of Outcome objects for each command.

import time

from tox.execute import Outcome
from tox.plugin import impl
from tox.tox_env.api import ToxEnv

_start: float = 0


@impl
def tox_before_run_commands(tox_env: ToxEnv) -> None:
    global _start  # noqa: PLW0603
    _start = time.monotonic()


@impl
def tox_after_run_commands(
    tox_env: ToxEnv, exit_code: int, outcomes: list[Outcome]
) -> None:
    elapsed = time.monotonic() - _start
    tox_env.conf.core["report"].verbosity0(
        f"{tox_env.conf['env_name']} finished in {elapsed:.1f}s "
        f"(exit code {exit_code})",
    )

Intercept package installations

Use tox_on_install to run logic whenever tox installs packages (deps, the project itself, etc.). The section and of_type parameters identify which install phase triggered the call.

from typing import Any

from tox.plugin import impl
from tox.tox_env.api import ToxEnv


@impl
def tox_on_install(tox_env: ToxEnv, arguments: Any, section: str, of_type: str) -> None:
    print(f"Installing [{section}] {of_type}: {arguments}")  # noqa: T201

Clean up after an environment

Use tox_env_teardown to run cleanup logic after an environment finishes, regardless of whether commands succeeded or failed.

from tox.plugin import impl
from tox.tox_env.api import ToxEnv


@impl
def tox_env_teardown(tox_env: ToxEnv) -> None:
    cache_dir = tox_env.env_dir / ".cache"
    if cache_dir.exists():
        import shutil

        shutil.rmtree(cache_dir)

Register a custom environment runner

Use tox_register_tox_env to register a custom run or package environment type. This is the hook used by plugins like tox-uv to replace the default virtualenv-based runner.

from tox.plugin import impl
from tox.tox_env.python.virtual_env.runner import VirtualEnvRunner
from tox.tox_env.register import ToxEnvRegister


class MyRunner(VirtualEnvRunner):
    @staticmethod
    def id() -> str:
        return "my-runner"

    # override methods to customize behavior


@impl
def tox_register_tox_env(register: ToxEnvRegister) -> None:
    register.add_run_env(MyRunner)

Set runner = my-runner in a tox environment to use it.

Package a plugin for distribution

While toxfile.py works for project-local plugins, distributable plugins are standard Python packages that declare the tox entry point.

In pyproject.toml:

[project]
name = "tox-myplugin"
version = "1.0.0"
dependencies = ["tox>=4"]

[project.entry-points.tox]
myplugin = "tox_myplugin"

In src/tox_myplugin/__init__.py, define your hooks exactly as in toxfile.py:

from tox.plugin import impl


@impl
def tox_append_version_info() -> str:
    return "myplugin-1.0.0"

After pip install tox-myplugin, tox discovers the plugin automatically via the entry point.

Extension points

Plugin management for tox using pluggy.

Pluggy discovers a plugin by looking up for entry-points named tox, for example in a pyproject.toml:

[project.entry-points.tox]
your_plugin = "your_plugin.hooks"

Therefore, to start using a plugin, you solely need to install it in the same environment tox is running in and it will be discovered via the defined entry-point (in the example above, tox will load your_plugin.hooks).

A plugin is created by implementing extension points in the form of hooks. For example the following code snippet would define a new --magic command line interface flag the user can specify:

from tox.config.cli.parser import ToxParser
from tox.plugin import impl


@impl
def tox_add_option(parser: ToxParser) -> None:
    parser.add_argument("--magic", action="store_true", help="magical flag")

You can define such hooks either in a package installed alongside tox or within a toxfile.py found alongside your tox configuration file (root of your project).

tox.plugin.NAME = 'tox'

the name of the tox hook

tox.plugin.impl

decorator to mark tox plugin hooks

tox.plugin.spec.tox_add_core_config(core_conf, state)

Called when the core configuration is built for a tox environment.

Parameters:
  • core_conf (ConfigSet) – the core configuration object

  • state (State) – the global tox state object

Return type:

None

tox.plugin.spec.tox_add_env_config(env_conf, state)

Called when configuration is built for a tox environment.

Parameters:
  • env_conf (EnvConfigSet) – the core configuration object

  • state (State) – the global tox state object

Return type:

None

tox.plugin.spec.tox_add_option(parser)

Add a command line argument.

This is the first hook to be called, right after the logging setup and config source discovery.

Parameters:

parser (ToxParser) – the command line parser

Return type:

None

tox.plugin.spec.tox_after_run_commands(tox_env, exit_code, outcomes)

Called after the commands set is executed.

Parameters:
  • tox_env (ToxEnv) – the tox environment being executed

  • exit_code (int) – exit code of the command

  • outcomes (list[Outcome]) – outcome of each command execution

Return type:

None

tox.plugin.spec.tox_before_run_commands(tox_env)

Called before the commands set is executed.

Parameters:

tox_env (ToxEnv) – the tox environment being executed

Return type:

None

tox.plugin.spec.tox_env_teardown(tox_env)

Called after a tox environment has been teared down.

Parameters:

tox_env (ToxEnv) – the tox environment

Return type:

None

tox.plugin.spec.tox_extend_envs()

Declare additional environment names.

Added in version 4.29.0.

This hook is called without any arguments early in the lifecycle. It is expected to return an iterable of strings with environment names for tox to consider. It can be used to facilitate dynamic creation of additional environments from within tox plugins.

This is ideal to pair with tox_add_core_config that has access to state.conf.memory_seed_loaders allowing to extend it with instances of tox.config.loader.memory.MemoryLoader early enough before tox starts caching configuration values sourced elsewhere.

Return type:

Iterable[str]

tox.plugin.spec.tox_on_install(tox_env, arguments, section, of_type)

Called before executing an installation command.

Parameters:
  • tox_env (ToxEnv) – the tox environment where the command runs in

  • arguments (Any) – installation arguments

  • section (str) – section of the installation

  • of_type (str) – type of the installation

Return type:

None

tox.plugin.spec.tox_register_tox_env(register)

Register new tox environment type. You can register:

  • run environment: by default this is a local subprocess backed virtualenv Python

  • packaging environment: by default this is a PEP-517 compliant local subprocess backed virtualenv Python

Parameters:

register (ToxEnvRegister) – a object that can be used to register new tox environment types

Return type:

None

Adopting a plugin under the tox-dev organization

You’re free to host your plugin on your favorite platform. However, the core tox development happens on GitHub under the tox-dev organization. We are happy to adopt tox plugins under the tox-dev organization if:

  • the plugin solves a valid use case and is not malicious,

  • it’s released on PyPI with at least 100 downloads per month (to ensure it’s actively used).

What’s in it for you:

  • you get owner rights on the repository under the tox-dev organization,

  • exposure of your plugin under the core umbrella,

  • backup maintainers from other tox plugin developers.

How to apply: