Plugin Structure
A well-structured plugin follows consistent conventions and organization.
Directory Structure
Complete plugin structure example:
plugins/
└── my-plugin/
├── plugin.json # Required: Plugin manifest
├── Bootstrap.php # Optional: Initialization class
├── composer.json # Optional: Composer dependencies
├── Migrations/ # Database migrations
│ ├── Version20251214000001.php
│ └── Version20251214000002.php
├── Resources/
│ ├── config/
│ │ └── services.yaml # Service definitions
│ ├── translations/
│ │ ├── messages.en.yaml # English translations
│ │ └── messages.pl.yaml # Polish translations
│ └── views/ # Optional additional views
├── src/
│ ├── Controller/ # HTTP controllers
│ │ ├── HelloController.php
│ │ └── Admin/ # EasyAdmin CRUD controllers
│ │ └── MyCrudController.php
│ ├── Entity/ # Doctrine entities
│ │ └── MyEntity.php
│ ├── Repository/ # Doctrine repositories
│ │ └── MyEntityRepository.php
│ ├── Service/ # Business logic
│ │ └── MyService.php
│ ├── Widget/ # Dashboard widgets
│ │ └── MyWidget.php
│ ├── EventSubscriber/ # Event listeners
│ │ └── MenuEventSubscriber.php
│ ├── Command/ # Console commands
│ │ └── MyCommand.php
│ ├── CronTask/ # Scheduled tasks
│ │ └── MyCleanupTask.php
│ ├── Tab/ # Server tabs
│ │ └── MyTab.php
│ ├── Provider/ # Payment providers
│ │ └── MyPaymentProvider.php
│ ├── Adapter/ # External API adapters
│ │ └── ExternalApiAdapter.php
│ ├── DTO/ # Data Transfer Objects
│ │ └── MyDTO.php
│ └── Exception/ # Custom exceptions
│ └── MyPluginException.php
├── templates/ # Twig templates
│ ├── index.html.twig
│ ├── widgets/
│ │ └── my_widget.html.twig
│ └── tabs/
│ └── my_tab.html.twig
├── translations/ # Plugin translations (alternative location)
│ └── plugin_my_plugin.en.yaml
├── assets/ # Frontend assets
│ ├── css/
│ │ └── styles.css
│ ├── js/
│ │ └── script.js
│ └── images/
│ └── logo.svg
└── tests/ # Unit and integration tests
├── Unit/
└── Integration/Required Files
Every plugin must have:
plugin.json: Plugin manifest with metadata and configuration
Optional Files
Bootstrap.php: Initialization logic when plugin is enabled/disabled
composer.json: External package dependencies
services.yaml: Service container configuration
File Organization Best Practices
Separate concerns: Controllers, services, entities in separate directories
Follow PSR-4: Namespace must match directory structure
Use consistent naming: Follow Symfony and PteroCA conventions
Group by feature: Related classes in same directory
Keep templates near code: Easy to locate and maintain
PSR-4 Autoloading
Namespace must match directory structure:
Incorrect namespace = "Class not found" errors!
Related Guides
Plugin Manifest - Configure plugin.json
Dependencies - Managing services and dependencies
Controllers & Routes - Web interface
Entities & Database - Data layer
Last updated