Each plugin is an independent directory with the following structure:
plugins/
└── YourPlugin/ # Plugin directory (PascalCase naming)
├── Plugin.php # Main plugin class (required)
├── config.json # Plugin configuration (required)
├── routes/
│ └── api.php # API routes
├── Controllers/ # Controllers directory
│ └── YourController.php
├── Commands/ # Artisan commands directory
│ └── YourCommand.php
└── README.md # Documentation
{
"name": "My Plugin",
"code": "my_plugin", // Corresponds to plugin directory (lowercase + underscore)
"version": "1.0.0",
"description": "Plugin functionality description",
"author": "Author Name",
"require": {
"xboard": ">=1.0.0" // Version not fully implemented yet
},
"config": {
"api_key": {
"type": "string",
"default": "",
"label": "API Key",
"description": "API Key"
},
"timeout": {
"type": "number",
"default": 300,
"label": "Timeout (seconds)",
"description": "Timeout in seconds"
}
}
}<?php
namespace Plugin\YourPlugin;
use App\Services\Plugin\AbstractPlugin;
class Plugin extends AbstractPlugin
{
/**
* Called when plugin starts
*/
public function boot(): void
{
// Register frontend configuration hook
$this->filter('guest_comm_config', function ($config) {
$config['my_plugin_enable'] = true;
$config['my_plugin_setting'] = $this->getConfig('api_key', '');
return $config;
});
}
}Recommended approach: Extend PluginController
<?php
namespace Plugin\YourPlugin\Controllers;
use App\Http\Controllers\PluginController;
use Illuminate\Http\Request;
class YourController extends PluginController
{
public function handle(Request $request)
{
// Get plugin configuration
$apiKey = $this->getConfig('api_key');
$timeout = $this->getConfig('timeout', 300);
// Your business logic...
return $this->success(['message' => 'Success']);
}
}<?php
use Illuminate\Support\Facades\Route;
use Plugin\YourPlugin\Controllers\YourController;
Route::group([
'prefix' => 'api/v1/your-plugin'
], function () {
Route::post('/handle', [YourController::class, 'handle']);
});In controllers, you can easily access plugin configuration:
// Get single configuration
$value = $this->getConfig('key', 'default_value');
// Get all configurations
$allConfig = $this->getConfig();
// Check if plugin is enabled
$enabled = $this->isPluginEnabled();XBoard has built-in hooks for many business-critical nodes. Plugin developers can flexibly extend through filter or listen methods. Here are the most commonly used and valuable hooks:
| Hook Name | Type | Typical Parameters | Description |
|---|---|---|---|
| user.register.before | action | Request | Before user registration |
| user.register.after | action | User | After user registration |
| user.login.after | action | User | After user login |
| user.password.reset.after | action | User | After password reset |
| order.cancel.before | action | Order | Before order cancellation |
| order.cancel.after | action | Order | After order cancellation |
| payment.notify.before | action | method, uuid, request | Before payment callback |
| payment.notify.verified | action | array | Payment callback verification successful |
| payment.notify.failed | action | method, uuid, request | Payment callback verification failed |
| traffic.reset.after | action | User | After traffic reset |
| ticket.create.after | action | Ticket | After ticket creation |
| ticket.reply.user.after | action | Ticket | After user replies to ticket |
| ticket.close.after | action | Ticket | After ticket closure |
⚡️ The hook system will continue to expand. Developers can always follow this documentation and the
php artisan hook:listcommand to get the latest supported hooks.
Used to modify data:
// In Plugin.php boot() method
$this->filter('guest_comm_config', function ($config) {
// Add configuration for frontend
$config['my_setting'] = $this->getConfig('setting');
return $config;
});Used to execute operations:
$this->listen('user.created', function ($user) {
// Operations after user creation
$this->doSomething($user);
});Using TelegramLogin plugin as an example to demonstrate complete implementation:
Main Plugin Class (23 lines):
<?php
namespace Plugin\TelegramLogin;
use App\Services\Plugin\AbstractPlugin;
class Plugin extends AbstractPlugin
{
public function boot(): void
{
$this->filter('guest_comm_config', function ($config) {
$config['telegram_login_enable'] = true;
$config['telegram_login_domain'] = $this->getConfig('domain', '');
$config['telegram_bot_username'] = $this->getConfig('bot_username', '');
return $config;
});
}
}Controller (extends PluginController):
class TelegramLoginController extends PluginController
{
public function telegramLogin(Request $request)
{
// Check plugin status
if ($error = $this->beforePluginAction()) {
return $error[1];
}
// Get configuration
$botToken = $this->getConfig('bot_token');
$timeout = $this->getConfig('auth_timeout', 300);
// Business logic...
return $this->success($result);
}
}Plugins can register their own scheduled tasks by implementing the schedule(Schedule $schedule) method in the main class.
Example:
use Illuminate\Console\Scheduling\Schedule;
class Plugin extends AbstractPlugin
{
public function schedule(Schedule $schedule): void
{
// Execute every hour
$schedule->call(function () {
// Your scheduled task logic
\Log::info('Plugin scheduled task executed');
})->hourly();
}
}- Just implement the
schedule()method in Plugin.php. - All plugin scheduled tasks will be automatically scheduled by the main program.
- Supports all Laravel scheduler usage.
Plugins can automatically register Artisan commands by creating command classes in the Commands/ directory.
plugins/YourPlugin/
├── Commands/
│ ├── TestCommand.php # Test command
│ ├── BackupCommand.php # Backup command
│ └── CleanupCommand.php # Cleanup command
Example: TestCommand.php
<?php
namespace Plugin\YourPlugin\Commands;
use Illuminate\Console\Command;
class TestCommand extends Command
{
protected $signature = 'your-plugin:test {action=ping} {--message=Hello}';
protected $description = 'Test plugin functionality';
public function handle(): int
{
$action = $this->argument('action');
$message = $this->option('message');
try {
return match ($action) {
'ping' => $this->ping($message),
'info' => $this->showInfo(),
default => $this->showHelp()
};
} catch (\Exception $e) {
$this->error('Operation failed: ' . $e->getMessage());
return 1;
}
}
protected function ping(string $message): int
{
$this->info("✅ {$message}");
return 0;
}
protected function showInfo(): int
{
$this->info('Plugin Information:');
$this->table(
['Property', 'Value'],
[
['Plugin Name', 'YourPlugin'],
['Version', '1.0.0'],
['Status', 'Enabled'],
]
);
return 0;
}
protected function showHelp(): int
{
$this->info('Usage:');
$this->line(' php artisan your-plugin:test ping --message="Hello" # Test');
$this->line(' php artisan your-plugin:test info # Show info');
return 0;
}
}- ✅ Automatically register all commands in
Commands/directory when plugin is enabled - ✅ Command namespace automatically set to
Plugin\YourPlugin\Commands - ✅ Supports all Laravel command features (arguments, options, interaction, etc.)
# Test command
php artisan your-plugin:test ping --message="Hello World"
# Show information
php artisan your-plugin:test info
# View help
php artisan your-plugin:test --help- Command Naming: Use
plugin-name:actionformat, e.g.,telegram:test - Error Handling: Wrap main logic with try-catch
- Return Values: Return 0 for success, 1 for failure
- User Friendly: Provide clear help information and error messages
- Type Declarations: Use PHP 8.2 type declarations
Method 1: Extend PluginController (Recommended)
- Automatic configuration access:
$this->getConfig() - Automatic status checking:
$this->beforePluginAction() - Unified error handling
Method 2: Use HasPluginConfig Trait
use App\Http\Controllers\Controller;
use App\Traits\HasPluginConfig;
class YourController extends Controller
{
use HasPluginConfig;
public function handle()
{
$config = $this->getConfig('key');
// ...
}
}Supported configuration types:
string- Stringnumber- Numberboolean- Booleanjson- Arrayyaml
- Plugin main class should be as concise as possible
- Mainly used for registering hooks and routes
- Complex logic should be placed in controllers or services
- Define all configuration items in
config.json - Use
$this->getConfig()to access configuration - Provide default values for all configurations
- Use semantic route prefixes
- Place API routes in
routes/api.php - Place Web routes in
routes/web.php
public function handle(Request $request)
{
// Check plugin status
if ($error = $this->beforePluginAction()) {
return $error[1];
}
try {
// Business logic
return $this->success($result);
} catch (\Exception $e) {
return $this->fail([500, $e->getMessage()]);
}
}\Log::info('Plugin operation', ['data' => $data]);
\Log::error('Plugin error', ['error' => $e->getMessage()]);// Check required configuration
if (!$this->getConfig('required_key')) {
return $this->fail([400, 'Missing configuration']);
}if (config('app.debug')) {
// Detailed debug information for development environment
}- Installation: Validate configuration, register to database
- Enable: Load plugin, register hooks and routes
- Running: Handle requests, execute business logic
Based on TelegramLogin plugin practical experience:
- Simplicity: Main class only 23 lines, focused on core functionality
- Practicality: Extends PluginController, convenient configuration access
- Maintainability: Clear directory structure, standard development patterns
- Extensibility: Hook-based architecture, easy to extend functionality
Following this guide, you can quickly develop plugins with complete functionality and concise code! 🚀
✅ Auto Registration: Automatically register all commands in Commands/ directory when plugin is enabled
✅ Namespace Isolation: Each plugin's commands use independent namespaces
✅ Type Safety: Support PHP 8.2 type declarations
✅ Error Handling: Comprehensive exception handling and error messages
✅ Configuration Integration: Commands can access plugin configuration
✅ Interaction Support: Support user input and confirmation operations
# Test Bot connection
php artisan telegram:test ping
# Send message
php artisan telegram:test send --message="Hello World"
# Get Bot information
php artisan telegram:test info# Show all statistics
php artisan telegram-extra:stats all
# User statistics
php artisan telegram-extra:stats users
# JSON format output
php artisan telegram-extra:stats users --format=json# Basic usage
php artisan example:hello
# With arguments and options
php artisan example:hello Bear --message="Welcome!"// ✅ Recommended: Use plugin name as prefix
protected $signature = 'telegram:test {action}';
protected $signature = 'telegram-extra:stats {type}';
protected $signature = 'example:hello {name}';
// ❌ Avoid: Use generic names
protected $signature = 'test {action}';
protected $signature = 'stats {type}';public function handle(): int
{
try {
// Main logic
return $this->executeAction();
} catch (\Exception $e) {
$this->error('Operation failed: ' . $e->getMessage());
return 1;
}
}// Get user input
$chatId = $this->ask('Please enter chat ID');
// Confirm operation
if (!$this->confirm('Are you sure you want to execute this operation?')) {
$this->info('Operation cancelled');
return 0;
}
// Choose operation
$action = $this->choice('Choose operation', ['ping', 'send', 'info']);// Access plugin configuration in commands
protected function getConfig(string $key, $default = null): mixed
{
// Get plugin instance through PluginManager
$plugin = app(\App\Services\Plugin\PluginManager::class)
->getEnabledPlugins()['example_plugin'] ?? null;
return $plugin ? $plugin->getConfig($key, $default) : $default;
}// One plugin can have multiple commands
plugins/YourPlugin/Commands/
├── BackupCommand.php # Backup command
├── CleanupCommand.php # Cleanup command
├── StatsCommand.php # Statistics command
└── TestCommand.php # Test command// Share data between commands through cache or database
Cache::put('plugin:backup:progress', $progress, 3600);
$progress = Cache::get('plugin:backup:progress');// Call commands in plugin's schedule method
public function schedule(Schedule $schedule): void
{
$schedule->command('your-plugin:backup')->daily();
$schedule->command('your-plugin:cleanup')->weekly();
}# View command help
php artisan your-plugin:command --help
# Verbose output
php artisan your-plugin:command --verbose
# Debug mode
php artisan your-plugin:command --debug// Log in commands
Log::info('Plugin command executed', [
'command' => $this->signature,
'arguments' => $this->arguments(),
'options' => $this->options()
]);// Record command execution time
$startTime = microtime(true);
// ... execution logic
$endTime = microtime(true);
$this->info("Execution time: " . round(($endTime - $startTime) * 1000, 2) . "ms");A: Check if plugin is enabled and ensure Commands/ directory exists and contains valid command classes.
A: Check if command class namespace is correct and ensure it extends Illuminate\Console\Command.
A: Get plugin instance through PluginManager, then call getConfig() method.
A: Yes, use Artisan::call() method to call other commands.
Artisan::call('other-plugin:command', ['arg' => 'value']);The plugin command system provides powerful extension capabilities for XBoard:
- 🚀 Development Efficiency: Quickly create management commands
- 🔧 Operational Convenience: Automate daily operations
- 📊 Monitoring Capability: Real-time system status viewing
- 🛠️ Debug Support: Convenient problem troubleshooting tools
By properly using plugin commands, you can greatly improve system maintainability and user experience! 🎉