Contributing commands, plugins and plugin-extensions
Overview
Adding commands like the dev-json command, creating new dev-json generators (plugins), or extending existing behavior is intentionally simple.
Before contributing, it is recommended to read the Plugin System overview
for a full explanation of how plugins and extensions are discovered and used.
This guide focuses on practical steps for contributors who want to implement their own:
command like the
dev-jsoncommand (e.g.devc my-cool-command),plugin for an existing command. For example the
ros2-desktop-fullplugin for thedev-jsoncommand (e.g.devc dev-json my-plugin) :plugin-extension like the
--nvidiaflag for the plugins of thedev-jsoncommand that can be reused by multiple plugins (e.g.devc dev-json ros2-desktop-full --my-extension).
Contributions should follow the existing directory layout:
devc_plugins/pluginsfor full plugins (e.g.ros2)devc_plugins/plugin_extensionsfor small reusable extensions (e.g.--nvidia)
Creating a Plugin
Note
The following sections explains how to create a new plugin with the dev-json command as example
A dev-json plugin produces a devcontainer.json file using templates and
optionally activates extensions. The easiest way to start is by subclassing
DevJsonPluginBase. Only two methods matter in the beginning:
_extend_base_arguments– define your plugin-specific CLI flags_get_direct_json_patch– return JSON patches applied to the final output
Fist create a file in devc_plugins/plugins/my_plugin/my_plugin.py.Then create a minimal plugin that inserts a custom environment variable:
# my_plugin/my_plugin.py
from typing import Any
import argparse
from devc_plugins.plugins.dev_json_plugin_base import DevJsonPluginBase
class MyDevJsonPlugin(DevJsonPluginBase):
"""Example dev-json plugin adding ENV_FOO."""
def _extend_base_arguments(self, parser: argparse.ArgumentParser, cli_name: str) -> None:
parser.add_argument(
"--foo",
help="Value injected into container as ENV_FOO.",
default="bar",
)
def _get_direct_json_patch(self, args: argparse.Namespace) -> dict[str, Any]:
return {
"containerEnv": {
"ENV_FOO": args.foo
}
}
To register the plugin, add it to pyproject.toml:
[project.entry-points."devc_commands.dev_json.plugins"]
... other plugins
my-example = "devc_plugins.plugins.my_plugin.my_plugin:MyDevJsonPlugin"
After installation, it appears automatically:
devc dev-json my-example --foo hello
This generates a devcontainer.json with ENV_FOO=hello. No extra plumbing
is required. The template rendering, extension system, and filesystem handling
are all managed by the plugin base class.
Creating a Plugin Extension
Note
The following sections explains how to create a new plugin-extension with the dev-json command as example
Extensions inject optional behavior into existing dev-json plugins. For
example, SSH handling, GPU flags, or USB passthrough are implemented as
extensions instead of full plugins. Extensions are useful when:
a feature should work with multiple dev-json plugins,
the change is small (a few JSON lines),
the feature should be activated via a simple CLI flag.
Extensions subclass DevJsonPluginExtension. They must implement:
_get_devcontainer_updates– return the JSON patch contributed_register_arguments– define flags such as--my-extension
First create the a file devc_plugins/plugin_extensions/dev_json_extensions/my_extension.p. A minimal extension enabling a custom mount:
# dev_json_extensions/my_extension.py
from typing import Any
import argparse
from devc_plugins.plugin_extensions.dev_json_extensions import DevJsonPluginExtension
class MyMountExtension(DevJsonPluginExtension):
name = "my-mount"
def _register_arguments(self, parser: argparse.ArgumentParser, defaults: dict) -> None:
parser.add_argument(
MyMountExtension.as_arg_name(),
help="Mount ~/data into the container.",
action="store_true",
)
def _get_devcontainer_updates(self, cliargs: argparse.Namespace) -> dict[str, Any]:
print(MyMountExtension.as_arg_name())
flag = cliargs.get(MyMountExtension.get_name(), None)
print(cliargs)
print(flag)
if not flag:
return {}
return {
"mounts": [
"source=${env:HOME}/data,target=/workspace/data,type=bind"
]
}
Register the extension in pyproject.toml:
[project.entry-points."devc_commands.dev_json.plugins.extensions"]
... other plugin extensions
my-extension = "devc_plugins.plugin_extensions.dev_json_extensions.my_extension:MyMountExtension"
Now any dev-json plugin automatically supports the new flag:
devc dev-json base-setup --my-mount
When to Use a Plugin vs. an Extension
Use a plugin when providing a new devcontainer generator (e.g. ROS 2, language-specific setups).
Use an extension when adding optional flags that inject small JSON fragments (e.g. environment variables, mounts, authentication, GPU settings).