Project Structure
In practice, FrameX projects usually fall into two different structures.
You should choose the structure based on what you are publishing and how many plugins the project contains.
Structure 1: Single Plugin Package
Use this structure when the project itself is one plugin package.
This is a good fit when you want to:
- build one reusable plugin
- publish it to PyPI
- let other FrameX projects load it directly by import path
Recommended layout:
your_plugin/
├── pyproject.toml
├── README.md
├── src/
│ └── your_plugin/
│ ├── __init__.py
│ ├── config.py
│ ├── models.py
│ └── service.py
└── tests/
In this structure, the plugin entry is usually defined in:
src/your_plugin/__init__.py
That means other projects can load it directly, for example:
framex run --load-plugins your_plugin
Or in config.toml:
load_plugins = ["your_plugin"]
When to use it
Choose this structure when:
- the package contains one plugin
- the plugin is intended to be reused across projects
- you want the package name itself to be the plugin import path
Why it works
Because the whole package is the plugin, using src/your_plugin/__init__.py as the plugin entry is natural here.
The package is small, self-contained, and meant to be loaded as one unit.
Structure 2: Multi-Plugin Project
Use this structure when one project contains multiple plugins.
This is a good fit when you want to:
- build several capabilities in one service
- let different people or teams own different plugins
- load only part of the project in different environments
Recommended layout:
your_project/
├── pyproject.toml
├── config.toml
├── README.md
├── src/
│ └── your_project/
│ ├── __init__.py
│ ├── __main__.py
│ ├── consts.py
│ └── plugins/
│ ├── __init__.py
│ ├── foo.py
│ └── bar/
│ ├── __init__.py
│ ├── config.py
│ └── service.py
└── tests/
In this structure, plugins usually live under:
src/your_project/plugins/
Typical import paths are:
your_project.plugins.fooyour_project.plugins.bar
Examples:
framex run --load-plugins your_project.plugins.foo
load_plugins = [
"your_project.plugins.foo",
"your_project.plugins.bar",
]
When to use it
Choose this structure when:
- one project contains multiple plugins
- different capabilities should stay clearly separated
- you want cleaner ownership boundaries inside one codebase
Why it works
Because the project package and the plugin modules are different things.
your_projectis the application packageyour_project.plugins.*are the plugin packages or modules
That separation makes the codebase easier to understand and maintain.
Single File vs Package Plugin
Inside a multi-plugin project, each plugin can still be organized in two ways.
Single-file plugin
src/your_project/plugins/
└── foo.py
Use this when the plugin is still small.
Package plugin
src/your_project/plugins/
└── bar/
├── __init__.py
├── config.py
├── models.py
└── service.py
Use this when the plugin is larger and needs multiple modules.
Built-In Plugins vs Your Plugins
Keep built-in plugins and your own plugins separate.
- built-in plugins come from
framex.plugins, such asechoandproxy - your own plugins come from your own package
Example:
framex run \
--load-builtin-plugins echo \
--load-plugins your_project.plugins.foo
Rule of Thumb
Use this simple rule:
- if the package itself is one reusable plugin, put the plugin in
src/your_plugin/__init__.py - if the project contains multiple plugins, create
src/your_project/plugins/ - if one plugin grows large, turn it from one file into one package