Writing your own manifest

You have a pytest plugin the deck doesn’t curate, and you’d like it as a switch in the sidebar. One TOML file in your repo does it. No code, no fork.

This page walks through building that file. For every key and its rules, see the manifest reference.

Where the file goes

Put manifests in this directory under your project root (the pytest rootdir):

<rootdir>/.pytest-deck/plugins/*.toml

One manifest per .toml file. Commit them alongside your tests; they’re part of how your suite is meant to be run.

The deck reads this directory when the dashboard page loads. A file you add while the deck is running shows up after you refresh the browser tab.

Start with a plain switch

The smallest useful manifest turns a plugin on and off, nothing more:

# .pytest-deck/plugins/my-plugin.toml
id = "my_plugin"
label = "My plugin"
dist = "my-pytest-plugin"
scope = "run"

That’s the whole file. When the switch is on, the deck adds -p my_plugin to the pytest argv for the run. Every manifest is a switch first: any config fields you add later append their tokens after that -p.

Find the entry-point name

id must be the plugin’s pytest11 entry-point name, the exact token -p loads. That’s often not the name you install: pytest-json-report (dist) registers pytest_jsonreport, and pytest-timeout registers plain timeout. To read the real names straight from your environment:

python -c "from importlib.metadata import entry_points; print([e.name for e in entry_points(group='pytest11')])"

That prints exactly the names -p resolves, for example ['deck', 'pytest_cov', 'timeout', 'pytest_mpl']. Find your plugin in the list. Use that string as id, and put the install name in dist.

Add config and a rendered report

Here’s a real, installable plugin: pytest-json-report, which writes your run’s results as a JSON document. This manifest adds a checkbox and pipes the plugin’s output into the Run info pane (what the detail pane shows when no test is pinned):

# .pytest-deck/plugins/json-report.toml
id = "pytest_jsonreport"
label = "JSON report"
dist = "pytest-json-report"
scope = "run"
render = "json"

[transport]
type = "json_file"
arg = "--json-report-file={tmpdir}/report.json"
path = "{tmpdir}/report.json"

[[fields]]
key = "enable"
label = "Emit report"
type = "bool"
default = true
arg = "--json-report"

When you enable this switch and run, the deck compiles:

-p pytest_jsonreport --json-report --json-report-file=<run-tmpdir>/report.json

{tmpdir} resolves to a temporary directory scoped to the run, so the report never lands in your working tree. After the child exits, the deck reads report.json from there and shows its parsed contents as a foldable JSON tree in the Run info pane.

Tip

pytest-json-report really does use the --json-report and --json-report-file flags shown here. When you adapt this for another plugin, check that plugin’s own flags. The deck emits whatever argv tokens your manifest declares, verbatim.

One rule saves the most debugging: each arg is a single argv token, so join a flag and its value with = (--reruns={value}, never --reruns {value}). The full compile rules are in the reference.

Know the limits

  • Every key, type, and default: the manifest reference.

  • What user manifests can’t declare (reserved env vars, curated-only transports, first-party renders): the trust model.

  • Rendered payloads are capped at 256 KiB: the render size cap.

Verify it loaded

Launch the deck (or refresh the tab if it’s already open) and look at the sidebar. Your manifest shows up as a switch labeled with your label. If it isn’t there, check:

  • The plugin is installed in the environment running the deck.

  • The file is in <rootdir>/.pytest-deck/plugins/ and ends in .toml.

  • The manifest validates: a rejected file is skipped, and the deck’s terminal prints a warning naming the file and the reason. Python prints each distinct warning once per process, so look for it the first time the page loads after you start the server (restart the server to see it again).

Fix the reason and refresh the browser tab. Collect alone won’t do it: the deck rescans the manifest directory on page load, not on collect.

Next steps