Plugin Structure

A fully-featured plugin uses all of the following directories and files. Only Plugin.php and plugin_info.json are required — everything else is added as your plugin needs it.

Full Directory Tree

plugins/MyPlugin/
├── Plugin.php                        ← required: implements PluginInterface
├── plugin_info.json                  ← required: manifest
├── Installer.php                     ← optional: up()/down() hooks
├── Config/
│   └── Routes.php
├── Controllers/
│   ├── Main.php
│   └── Admin/
│       └── Dashboard.php
├── Models/
│   └── MyPluginModel.php
├── Services/
│   └── MyPluginService.php
├── Database/
│   └── Migrations/
│       └── 2024-01-01-000001_CreateMyPluginTable.php
├── Views/
│   ├── store/                        ← public .tpl templates
│   │   └── index.tpl
│   └── admin/                        ← admin .php views
│       └── index.php
├── Language/
│   ├── en/MyPlugin.php
│   ├── es/MyPlugin.php
│   ├── fr/MyPlugin.php
│   ├── id/MyPlugin.php
│   ├── pt/MyPlugin.php
│   └── sk/MyPlugin.php
├── Api/
│   └── V1Controller.php
└── assets/
    ├── css/
    └── js/

File-by-File Conventions

Plugin.php

The entry point. Must be in the root of the plugin directory with namespace PluginsMyPlugin. Implements all 7 methods of PluginInterface. Keep this class thin — delegate to services for anything complex.

Installer.php

<?php

namespace PluginsMyPlugin;

class Installer
{
    public static function up(): void
    {
        // Called once on activation (after migrations)
    }

    public static function down(): void
    {
        // Called only on explicit uninstall (NOT on deactivation)
    }
}

Table Naming

PluginPrefixExample table
PvDocspvd_pvd_articles, pvd_versions
DStoreds_ds_products, ds_orders
Vettingvt_vt_queue, vt_results
MyPluginmp_mp_items, mp_settings

Admin Views

return view('Plugins\MyPlugin\Views\admin\index', $data);

Public Views

return $this->themeService->view('Plugins\MyPlugin\Views\store\index', $data);

Language Files

<?php
// Language/en/MyPlugin.php
return [
    'page_title'   => 'My Plugin',
    'no_results'   => 'No items found.',
    'save_success' => 'Settings saved successfully.',
];

Access via lang('MyPlugin.page_title') in PHP or {! lang "MyPlugin.page_title" !} in templates.

Assets

Static assets that need to be publicly accessible must be placed in public/plugins/{slug}/. The plugin directory itself is not web-accessible.