Plugins
Beta API Notice: The plugin system is currently in beta. While functional and actively used, the API may change in future versions as we gather feedback and improve the developer experience.
Mountaineer plugins are essentially reusable Mountaineer projects that can be shared across different applications. They encapsulate controllers, views, models, and frontend components into a distributable package that can be easily integrated into any Mountaineer application.
Think of plugins as mini-applications with their own:
- Controllers - Backend logic and API endpoints
- Views - Frontend React components and pages
- Models - Data structures and database schemas
- Static assets - CSS, images, and other resources
- Build configuration - Custom bundlers and compilation steps
The key benefit is dependency encapsulation and consistency - plugins are compiled ahead of time, ensuring they work reliably across different projects without version conflicts.
First-Party Plugins
We maintain open source plugins for the features every app rebuilds. Other ecosystems sell these as paid add-ons. Ours are free:
| Plugin | What you get |
|---|---|
| Auth | Email/password auth, sessions, password reset, admin users |
| Billing | Stripe subscriptions, metered usage, webhooks, checkout |
| Cloud | File storage and email across AWS, Cloudflare, DigitalOcean, and Resend |
Each plugin follows the same Mountaineer conventions: config mixins you inherit in your AppConfig, model mixins you subclass into concrete tables, and dependency injection through Depends. They also compose. A common production setup uses all three, with one User table carrying both auth and billing state:
from iceaxe import TableBase
from mountaineer_auth.models import UserAuthMixin
from mountaineer_billing.models import UserBillingMixin
class User(UserBillingMixin, UserAuthMixin, TableBase):
pass
The rest of this guide covers how the plugin system itself works, and how to build and distribute your own.
Motivating Examples
Here are some compelling use cases for Mountaineer plugins:
1. Development Tools & Debugging
- Exception handlers with beautiful stack traces (like
mountaineer-exceptions) - Database admin panels for inspecting and editing data
- API documentation generators that auto-update with your schemas
- Performance monitoring dashboards with real-time metrics
2. Authentication & Security
- OAuth providers (Google, GitHub, Discord login)
- RBAC systems with user management interfaces
- 2FA/MFA implementations with QR code generation
- Audit logging with searchable activity feeds
3. Content Management
- Blog engines with markdown editing and publishing workflows
- Media galleries with upload, resize, and organization features
- Comment systems with moderation and notifications
- CMS blocks for non-technical content editing
4. E-commerce & Payments
- Shopping cart systems with persistent sessions
- Payment processors (Stripe, PayPal integrations)
- Inventory management with stock tracking
- Order fulfillment workflows with shipping integrations
5. Communication & Notifications
- Email template systems with visual editors
- Chat widgets with real-time messaging
- Push notification services for mobile and web
- SMS/Webhook delivery systems
Plugin Structure
Let's examine the structure of mountaineer-exceptions as a reference implementation:
mountaineer-exceptions/
├── pyproject.toml # Package configuration
├── mountaineer_exceptions/
│ ├── __init__.py
│ ├── plugin.py # Main plugin definition
│ ├── cli.py # Build script for the plugin
│ ├── controllers/ # Backend controllers
│ │ ├── __init__.py
│ │ ├── exception_controller.py # Main controller logic
│ │ └── traceback.py # Supporting utilities
│ └── views/ # Frontend views
│ ├── __init__.py # View path utilities
│ ├── core/
│ │ ├── main.css # Plugin-specific styles
│ │ ├── layout.tsx # Layout components
│ │ └── exception/
│ │ ├── page.tsx # Main exception page
│ │ └── _server/ # Generated server hooks
│ └── _static/ # Compiled assets
└── README.md
Core Plugin Conventions
1. Plugin Definition File
Every plugin must have a plugin.py file that defines a MountaineerPlugin instance:
from fastapi import APIRouter
from mountaineer.client_compiler.postcss import PostCSSBundler
from mountaineer.plugin import BuildConfig, MountaineerPlugin
from your_plugin.controllers.main_controller import MainController
from your_plugin.views import get_core_view_path
router = APIRouter()
@router.get("/your-plugin/api/health")
def healthcheck():
return {"status": "ok"}
plugin = MountaineerPlugin(
name="your-plugin-name",
controllers=[MainController],
view_root=get_core_view_path(""),
build_config=BuildConfig(custom_builders=[PostCSSBundler()]),
router=router,
)
Required fields:
name: Unique identifier for your plugin
Optional fields:
controllers: List of controller classes to registerview_root: Path to the plugin's view directory. Required when plugin controllers use relativeview_pathvalues. If every controller already uses aManagedViewPathunder one shared root, Mountaineer can infer it.build_config: Custom build configuration with bundlers, CSS processors, etc.router: Optional FastAPIAPIRouterfor plugin-owned API endpoints that are not tied to a controller action
Plugins can be view-backed, router-only, or a mix of both. If your plugin only exposes a FastAPI router, you can omit both controllers and view_root.
2. View Path Management
Plugins should provide a utility function for resolving view paths:
from importlib.resources import as_file, files
from pathlib import Path
def get_core_view_path(asset_name: str) -> Path:
with as_file(files(__name__).joinpath(asset_name)) as path:
return Path(path)
This ensures your plugin's views can be located regardless of how the plugin is installed.
3. Controller Structure
Plugin controllers follow standard Mountaineer controller conventions:
from mountaineer.controller import ControllerBase
from mountaineer.paths import ManagedViewPath
from mountaineer.render import RenderBase
from your_plugin.views import get_core_view_path
class MainRender(RenderBase):
# Define your render data structure
message: str
items: list[dict]
class MainController(ControllerBase):
url = "/your-plugin" # Plugin routes should be namespaced
view_path = (
ManagedViewPath.from_view_root(get_core_view_path(""))
/ "core/main/page.tsx"
)
def render(self) -> MainRender:
return MainRender(
message="Hello from plugin!",
items=[{"id": 1, "name": "Example"}]
)
4. Frontend Views
Plugin views are standard React components with TypeScript:
import React from "react";
import { useServer } from "./_server/useServer";
const MainPage = (): JSX.Element => {
const serverState = useServer();
return (
<div className="p-8">
<h1 className="text-2xl font-bold">{serverState.message}</h1>
<ul className="mt-4">
{serverState.items.map((item) => (
<li key={item.id} className="py-2">
{item.name}
</li>
))}
</ul>
</div>
);
};
export default MainPage;
5. Build Configuration
Plugins should include a CLI build script for compilation:
from mountaineer.cli import handle_build
from your_plugin.plugin import plugin
app = plugin.to_webserver()
def build() -> None:
handle_build(webcontroller="your_plugin.cli:app")
And register it in pyproject.toml:
[project.scripts]
build-your-plugin = "your_plugin.cli:build"
6. Package Configuration
Your pyproject.toml should include build artifacts:
[tool.hatch.build]
artifacts = ["your_plugin/views/_static", "your_plugin/views/_ssr"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Creating a Plugin
1. Project Setup
mkdir my-awesome-plugin
cd my-awesome-plugin
# Initialize with uv
uv init
uv add mountaineer
2. Create Plugin Structure
mkdir -p my_awesome_plugin/{controllers,views/core/main}
touch my_awesome_plugin/__init__.py
touch my_awesome_plugin/plugin.py
touch my_awesome_plugin/cli.py
touch my_awesome_plugin/controllers/__init__.py
touch my_awesome_plugin/views/__init__.py
3. Implement Your Plugin
Follow the conventions outlined above to implement your controllers, views, and plugin definition.
4. Build Your Plugin
# Build the frontend assets
uv run build-my-awesome-plugin
# Package for distribution
uv build
Using a Plugin
1. Install the Plugin
# From PyPI (if published)
uv add my-awesome-plugin
# From local development
uv add --editable ../my-awesome-plugin
# From Git repository
uv add git+https://github.com/user/my-awesome-plugin.git
2. Register in Your App
from mountaineer.app import AppController
from my_awesome_plugin.plugin import plugin
controller = AppController(config=AppConfig())
# Mount the plugin (this registers all controllers automatically)
controller.register(plugin)
Use controller.register(plugin) instead of manually looping over plugin.controllers. This ensures plugin routers, static assets, and controller initialization all get wired up consistently.
3. Include Plugin Assets
If your plugin includes CSS or other static assets, make sure to include them in your app's metadata:
from mountaineer.render import LinkAttribute, Metadata
controller = AppController(
config=AppConfig(),
global_metadata=Metadata(
links=[
LinkAttribute(rel="stylesheet", href="/static/app_main.css"),
LinkAttribute(
rel="stylesheet",
href="/static_plugins/my-awesome-plugin/plugin_main.css",
),
]
),
)
If your plugin ships compiled frontend assets, they are mounted under /static_plugins/{plugin.name} rather than the host app's /static namespace.
Best Practices
Namespace Your Routes
Use descriptive URL prefixes to avoid conflicts:
class PluginController(ControllerBase):
url = "/my-plugin/dashboard" # Good: namespaced
# url = "/dashboard" # Bad: could conflict
Encapsulate Dependencies
Include all necessary dependencies in your plugin's pyproject.toml:
dependencies = [
"mountaineer>=0.1.0",
"pygments>=2.19.1", # Plugin-specific dependencies
]
Provide Clear Documentation
Include comprehensive README with:
- Installation instructions
- Configuration options
- Usage examples
- Screenshots or demos
Version Compatibility
Test your plugin against multiple Mountaineer versions and document compatibility:
dependencies = [
"mountaineer>=0.1.0,<1.0.0", # Specify version range
]
If your plugin depends on specific integration behavior, be explicit about that in your README as well. For example, note whether it works as a router-only plugin, whether it requires compiled view assets, and whether its controllers use relative view_path values or inferred ManagedViewPath roots.
Examples in the Wild
- mountaineer-auth: Login, signup, sessions, and password reset with pre-built views
- mountaineer-billing: Stripe subscriptions, metered usage, and webhook handling
- mountaineer-cloud: File storage and email across AWS, Cloudflare, DigitalOcean, and Resend
- mountaineer-exceptions: Beautiful exception handling and debugging
Contributing a Plugin
If you've built a useful plugin, consider sharing it with the community:
- Open source your plugin on GitHub
- Publish to PyPI for easy installation
- Document thoroughly with examples
- Submit to our community plugins list
The Mountaineer ecosystem thrives when developers can easily share and reuse common functionality. Your plugin could save hundreds of hours for someone out there!
The plugin system opens up endless possibilities for extending Mountaineer applications. Whether you're building internal tools, commercial add-ons, or open-source utilities, plugins provide a clean, reusable way to package and distribute Mountaineer functionality.
Ready to build your first plugin? Start with a simple example and gradually add more sophisticated features as you become familiar with the conventions.