Skip to content

Plugins

Plugins are shell scripts that register custom commands in dde. They allow you to create project-specific or globally available shortcuts without modifying the dde codebase.

Plugins are loaded from two locations:

LocationScope
~/.dde/plugins/Global — available in all projects
.dde/plugins/Project — available only in the current project

If a project plugin defines the same @command name as a global plugin, the project plugin takes precedence.

A plugin is a *.sh file with two required annotations in comments:

Terminal window
# @command <command-name>
# @description <human-readable description>

Everything after the annotations is the script body, executed when the command is invoked.

#!/usr/bin/env bash
# @command hello
# @description Say hello from the container
dde project:exec -s web echo "Hello from the web container"

This registers as dde project:exec:hello.

Arguments passed to the plugin command are forwarded as positional parameters ($1, $2, etc.):

#!/usr/bin/env bash
# @command web:hash-pw
# @description Generate a password hash
dde project:exec -s web -- php bin/console security:hash-password "$1"

Invoke with:

Terminal window
dde project:exec:web:hash-pw "my-secret-password"

Use colons in the @command name to create logical groupings:

Terminal window
# @command cache:clear
# @description Clear all application caches

This registers as dde project:exec:cache:clear.

Plugins are registered under the project:exec: namespace. The full command name is project:exec:<@command-value>.

@command valueRegistered as
hellodde project:exec:hello
web:hash-pwdde project:exec:web:hash-pw
cache:cleardde project:exec:cache:clear

All plugin commands appear in dde list output with their @description text.

Plugins run on the host machine via PluginProxyCommand. The plugin script is executed directly as a process with:

  • A 300-second timeout
  • TTY support when the terminal supports it (interactive commands work)
  • Arguments appended to the script invocation
  • Exit code forwarded to the caller
  • Only *.sh files are considered.
  • Files are sorted alphabetically by name within each directory.
  • Files without a @command annotation are silently ignored.
  • The @description annotation is optional (defaults to an empty string).
#!/usr/bin/env bash
# @command test
# @description Run the test suite
dde project:exec -s web php bin/phpunit
#!/usr/bin/env bash
# @command db:reset
# @description Drop and recreate database with fixtures
set -euo pipefail
dde project:exec -s web -- php bin/console doctrine:database:drop --force --if-exists
dde project:exec -s web -- php bin/console doctrine:database:create
dde project:exec -s web -- php bin/console doctrine:migrations:migrate --no-interaction
dde project:exec -s web -- php bin/console doctrine:fixtures:load --no-interaction
#!/usr/bin/env bash
# @command lint
# @description Run all linters
set -euo pipefail
dde project:exec -s web php vendor/bin/ecs check
dde project:exec -s web php vendor/bin/phpstan analyse
#!/usr/bin/env bash
# @command ports
# @description Show all exposed ports for the current project
docker compose ps --format "table {{.Name}}\t{{.Ports}}"

The plugin system consists of four classes:

  • PluginLoader — scans directories, parses annotations via regex (/^#\s*@(\w+)\s+(.+)$/m), returns PluginDefinition instances.
  • PluginDefinition — readonly DTO with command, description, scriptPath, and pluginDir properties.
  • PluginProxyCommand — Symfony Console command that wraps a PluginDefinition and executes the script via ProcessFactory. Validates that the script path resolves within the plugin directory (symlink traversal protection).
  • PluginCommandLoader — implements CommandLoaderInterface to lazily resolve plugin commands for the Symfony Console application.
  • Hooks — event-driven scripts tied to project lifecycle
  • Service Adapters — scripts that run inside containers at startup