The Plugin Directory Format
A plugin is a directory containing a manifest.toml and the Python source that
implements the triggers the manifest declares. This page specifies the
directory-layout contract the SDK validates: which file is the entry point, and
how declared triggers bind to Python functions.
This is a format contract in the same sense as the manifest and registry index formats: every consumer — the CLI that packages a plugin and the runtime that loads it — must agree on it.
Required files
manifest.tomlat the plugin root is required. See The Manifest Format.- A Python entry point at the plugin root (see below) is required.
Entry-point detection
The entry point is determined from the top-level regular files of the plugin directory. Subdirectories are not searched, and symbolic links are excluded.
- Multi-file plugin — the root contains
__init__.py. That file is the entry point; any number of helper modules may sit alongside it.__init__.pytakes priority over any other top-level.pyfile. - Single-file plugin — the root contains no
__init__.pyand exactly one top-level.pyfile. That file is the entry point. - No entry point — the root contains no top-level regular
.pyfile. This is a validation error. - Ambiguous — the root contains no
__init__.pybut two or more top-level.pyfiles. This is a validation error; add__init__.pyto declare a multi-file plugin, or keep only one.pyfile.
Detection rules:
- Non-
.pyfiles (for examplerequirements.txt,README.md) are ignored for entry-point detection. - Symbolic links are excluded (archives store the link target, not the link).
- Subdirectories are ignored; nested
.pyfiles (for examplepkg/helper.py) do not count, and a directory namedfoo.pyis not an entry point. - Matching is case-sensitive:
Foo.PYand__INIT__.pyare not treated asfoo.py/__init__.py.
Interaction with [plugin].exclude. Source-file
selection applies exclude patterns before entry-point detection runs. Only
the files that survive selection are considered when classifying the entry point;
excluded top-level .py files do not count. This means exclude can remove
what would otherwise be the sole entry point, yielding the ordinary “no entry
point” validation error.
Trigger binding
Each trigger declared in manifest.toml’s plugin.triggers must be
implemented as a top-level synchronous def <trigger>(...) in the entry
point.
- A top-level
def <trigger>(...)satisfies the trigger. - A top-level decorated function (
@decothendef <trigger>) counts — a decorator is not indirection. - An
async def <trigger>(...)is rejected: the runtime invokes trigger functions synchronously. - A definition that is not top-level does not count: class methods, nested
defs, defs guarded by
if/try, re-exports, and module-level assignments (<trigger> = something) all fail to bind. - If a name is defined more than once at the top level, the last definition wins (mirroring Python’s own rebind semantics).
- The entry-point source must parse as valid Python 3; a parse error is reported and no trigger checks run.
Diagnostics
A missing or malformed manifest.toml is reported on its own; entry-point
detection does not run, because the entry point is classified from the files
that survive source-file selection, and selection requires a valid manifest
(to apply [plugin].exclude patterns). When the manifest parses successfully,
validation continues: source-file selection, entry-point classification, and
trigger checks all run, and multiple cross-file diagnostics from that stage are
collected together so authors can fix everything in one pass.
Back: The Manifest Format | Next: The Registry Index Format