Waravel Plugin Development Guide
📋 Table of Contents
- Overview
- Plugin Structure
- Required Files
- Plugin Class Requirements
- View Requirements
- Settings Configuration
- Routes and Controllers
- Database Integration
- Best Practices
- Example Plugin
🎯 Overview
Waravel plugins extend the core functionality of the Waravel package. This guide provides comprehensive documentation for developing compatible plugins that integrate seamlessly with the Waravel platform.
📁 Plugin Structure
plugins/
└── YourPluginName/
├── plugin.json # Plugin metadata
├── YourPluginNamePlugin.php # Main plugin class
├── Controllers/ # HTTP controllers
├── Services/ # Business logic
├── Models/ # Database models
├── Jobs/ # Background jobs
├── Console/ # Artisan commands
├── database/ # Migrations
│ └── migrations/
├── resources/ # Views and assets
│ ├── views/
│ │ ├── dashboard/ # Main plugin interface
│ │ │ ├── index.blade.php # Main view (Start button target)
│ │ │ ├── details.blade.php # View Details content
│ │ │ └── settings.blade.php # Settings content
│ │ └── components/ # Reusable components
│ └── assets/ # CSS, JS, images
└── README.md # Plugin documentation📄 Required Files
1. plugin.json (Required)
{
"name": "Your Plugin Name",
"version": "1.0.0",
"description": "Brief description of your plugin functionality",
"author": "Your Name",
"author_email": "[email protected]",
"plugin_url": "https://yourwebsite.com/plugin",
"plugin_namespace": "YourPluginNamespace",
"dependencies": [],
"permissions": [
"permission_name_1",
"permission_name_2"
],
"settings": {
"setting_key": {
"type": "text|boolean|select|textarea",
"label": "Setting Label",
"description": "Setting description",
"required": true|false,
"default": "default_value",
"options": ["option1", "option2"] // for select type
}
},
"hooks": {
"hook_name": "YourPluginNamespace\\Hooks\\HookClass"
}
}2. Main Plugin Class (Required)
<?php
namespace YourPluginNamespace;
use Sobberrc\Waravel\Plugins\Plugin;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\View;
class YourPluginNamePlugin extends Plugin
{
public function __construct()
{
parent::__construct();
}
protected function initializePlugin(): void
{
$this->name = 'Your Plugin Name';
$this->version = '1.0.0';
$this->description = 'Plugin description';
$this->author = 'Your Name';
$this->authorEmail = '[email protected]';
$this->pluginUrl = 'https://yourwebsite.com/plugin';
$this->pluginNamespace = 'YourPluginNamespace';
$this->dependencies = [];
$this->permissions = ['permission_name'];
}
public function boot(): void
{
$this->registerRoutes();
$this->registerViews();
}
protected function onInstall(): void
{
// Plugin installation logic
}
protected function onActivate(): void
{
// Plugin activation logic
}
protected function onDeactivate(): void
{
// Plugin deactivation logic
}
protected function onUninstall(bool $keepData = false): void
{
// Plugin uninstallation logic
}
}🎨 View Requirements
1. Main View (Start Button Target)
File: resources/views/dashboard/index.blade.php
This is the main interface that users see when they click the "Start" button.
@extends('waravel::layouts.dashboard-layouts.dashboard-base')
@section('content')
<div class="p-6">
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold text-gray-800 mb-4">Your Plugin Name</h2>
<!-- Main plugin interface content -->
<div class="space-y-4">
<!-- Your plugin's main functionality here -->
</div>
</div>
</div>
@endsection2. Details View (View Details Button Target)
File: resources/views/dashboard/details.blade.php
This provides comprehensive information about the plugin.
@extends('waravel::layouts.dashboard-layouts.dashboard-base')
@section('content')
<div class="p-6">
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold text-gray-800 mb-4">Plugin Details</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="text-lg font-semibold mb-2">Plugin Information</h3>
<table class="w-full">
<tr><td class="font-medium">Name:</td><td>{{ $plugin->name }}</td></tr>
<tr><td class="font-medium">Version:</td><td>{{ $plugin->version }}</td></tr>
<tr><td class="font-medium">Author:</td><td>{{ $plugin->author }}</td></tr>
<tr><td class="font-medium">Status:</td><td>{{ $plugin->active ? 'Active' : 'Inactive' }}</td></tr>
</table>
</div>
<div>
<h3 class="text-lg font-semibold mb-2">Description</h3>
<p class="text-gray-600">{{ $plugin->description }}</p>
</div>
</div>
<!-- Additional details content -->
</div>
</div>
@endsection3. Settings View (Settings Button Target)
File: resources/views/dashboard/settings.blade.php
This provides the plugin's configuration interface.
@extends('waravel::layouts.dashboard-layouts.dashboard-base')
@section('content')
<div class="p-6">
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold text-gray-800 mb-4">Plugin Settings</h2>
<form method="POST" action="{{ route('waravel.plugins.settings.update', $plugin->name) }}">
@csrf
@foreach($settings as $key => $setting)
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">
{{ $setting['label'] }}
</label>
@if($setting['type'] === 'text')
<input type="text" name="settings[{{ $key }}]"
value="{{ $setting['value'] ?? $setting['default'] }}"
class="w-full px-3 py-2 border border-gray-300 rounded-md">
@elseif($setting['type'] === 'boolean')
<input type="checkbox" name="settings[{{ $key }}]" value="1"
{{ ($setting['value'] ?? $setting['default']) ? 'checked' : '' }}
class="mr-2">
@elseif($setting['type'] === 'select')
<select name="settings[{{ $key }}]"
class="w-full px-3 py-2 border border-gray-300 rounded-md">
@foreach($setting['options'] as $option)
<option value="{{ $option }}"
{{ ($setting['value'] ?? $setting['default']) === $option ? 'selected' : '' }}>
{{ $option }}
</option>
@endforeach
</select>
@endif
@if(isset($setting['description']))
<p class="text-sm text-gray-500 mt-1">{{ $setting['description'] }}</p>
@endif
</div>
@endforeach
<div class="flex justify-end">
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600">
Save Settings
</button>
</div>
</form>
</div>
</div>
@endsection⚙️ Settings Configuration
Define your plugin settings in plugin.json:
{
"settings": {
"api_key": {
"type": "text",
"label": "API Key",
"description": "Enter your API key for external service",
"required": true
},
"enable_feature": {
"type": "boolean",
"label": "Enable Feature",
"description": "Enable this feature",
"default": false
},
"processing_mode": {
"type": "select",
"label": "Processing Mode",
"description": "Choose how to process data",
"options": ["automatic", "manual", "scheduled"],
"default": "automatic"
}
}
}🛣️ Routes and Controllers
1. Register Routes in Plugin Class
protected function registerRoutes(): void
{
Route::middleware(['web', 'auth:waravel'])
->prefix('waravel/plugins/' . $this->pluginNamespace)
->name('waravel.plugins.' . $this->pluginNamespace . '.')
->group(function () {
Route::get('/', [YourController::class, 'index'])->name('index');
Route::get('/details', [YourController::class, 'details'])->name('details');
Route::get('/settings', [YourController::class, 'settings'])->name('settings');
Route::post('/settings', [YourController::class, 'updateSettings'])->name('settings.update');
});
}2. Controller Example
<?php
namespace YourPluginNamespace\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class YourController
{
public function index()
{
return view('your-plugin-namespace::dashboard.index');
}
public function details()
{
$plugin = $this->getPlugin();
return view('your-plugin-namespace::dashboard.details', compact('plugin'));
}
public function settings()
{
$plugin = $this->getPlugin();
$settings = $this->getPluginSettings();
return view('your-plugin-namespace::dashboard.settings', compact('plugin', 'settings'));
}
public function updateSettings(Request $request): JsonResponse
{
// Update plugin settings
return response()->json(['success' => true, 'message' => 'Settings updated']);
}
}🗄️ Database Integration
1. Create Migrations
// database/migrations/2024_01_01_000000_create_your_plugin_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('your_plugin_table', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('data');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('your_plugin_table');
}
};2. Create Models
<?php
namespace YourPluginNamespace\Models;
use Illuminate\Database\Eloquent\Model;
class YourModel extends Model
{
protected $table = 'your_plugin_table';
protected $fillable = [
'name',
'data'
];
}🎯 Best Practices
1. Naming Conventions
- Plugin Directory:
YourPluginName(PascalCase) - Plugin Class:
YourPluginNamePlugin.php - Namespace:
YourPluginNamespace - Routes:
waravel.plugins.your-plugin-name
2. Security
- Always validate user input
- Use CSRF protection for forms
- Implement proper authorization checks
- Sanitize data before database operations
3. Performance
- Use database indexes for frequently queried columns
- Implement caching where appropriate
- Use background jobs for heavy operations
- Optimize database queries
4. Error Handling
- Implement proper try-catch blocks
- Log errors for debugging
- Provide user-friendly error messages
- Handle edge cases gracefully
5. Documentation
- Include comprehensive README.md
- Document all public methods
- Provide usage examples
- Include troubleshooting guide
⚠️ Critical Troubleshooting Guide
🚨 Common Issues and Solutions
1. Plugin Detection Issues
Problem: Plugin not detected or "Plugin class not found" error
ERROR: No plugin file found {"plugin_path":"/path/to/plugin","plugin_name":"Plugin Name"}Solutions:
- ✅ Unified Naming: Ensure plugin directory name, main file name, class name, and
plugin.jsonname are all consistent - ✅ File Naming: Main plugin file must be
{PluginName}Plugin.php(e.g.,RedirectManagerPlugin.php) - ✅ Class Naming: Class must be
{PluginName}Plugin(e.g.,RedirectManagerPlugin) - ✅ Namespace: Must match in both file and
plugin.json
Example:
// Directory: plugins/RedirectManager/
// File: RedirectManagerPlugin.php
// Class: RedirectManagerPlugin
// Namespace: RedirectManager
// plugin.json: "name": "RedirectManager"2. View Namespace Issues
Problem: No hint path defined for [plugin-namespace] error
ERROR: No hint path defined for [redirect-manager]Solutions:
- ✅ Method Visibility: Make
registerViews()methodpublicin your plugin class - ✅ Service Provider: Ensure Waravel service provider calls
registerViews()on plugins - ✅ View Registration: Use correct namespace in
View::addNamespace()
Example:
public function registerViews(): void
{
View::addNamespace('redirect-manager', $this->pluginPath . '/resources/views');
}3. Route Registration Issues
Problem: Routes not working or 404 errors
ERROR: Target class [PluginNamespace\Controllers\Controller] does not existSolutions:
- ✅ Method Visibility: Make
registerRoutes()methodpublic - ✅ Manual Includes: Use
require_oncefor controllers in route closures - ✅ Route Conflicts: Avoid conflicts with Waravel's catch-all routes
- ✅ Controller Loading: Pass
$pluginPathto controller constructors
Example:
public function registerRoutes(): void
{
$pluginPath = $this->pluginPath;
Route::middleware(['web', 'auth:waravel'])
->prefix('waravel/plugins/your-plugin')
->name('waravel.plugins.your-plugin.')
->group(function () use ($pluginPath) {
Route::get('/', function () use ($pluginPath) {
require_once $pluginPath . '/Controllers/YourController.php';
$controller = new \YourPluginNamespace\Controllers\YourController($pluginPath);
return $controller->index();
})->name('index');
});
}4. Database Table Issues
Problem: Wrong table names or missing tables
ERROR: Base table or view not found: 1146 Table 'pages' doesn't existSolutions:
- ✅ Correct Table Names: Use
waravel_pagesinstead ofpages - ✅ Table Creation: Implement
ensureTablesExist()method in controllers - ✅ Migration Integration: Create tables on plugin activation
- ✅ Manual Creation: Fallback to manual table creation if migrations fail
Example:
private function ensureTablesExist(): void
{
if (!\Schema::hasTable('your_plugin_table')) {
\Schema::create('your_plugin_table', function ($table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
}5. Controller Return Type Issues
Problem: Return value must be of type Illuminate\View\View, Illuminate\Http\Response returned
ERROR: Controller method must return View, not ResponseSolutions:
- ✅ Consistent Return Types: Always return
Viewfrom controller methods - ✅ Error Handling: Use try-catch with View fallbacks, not Response
- ✅ Method Signatures: Match return type hints with actual returns
Example:
public function index(): View
{
try {
return view('plugin-namespace::dashboard.index', compact('data'));
} catch (\Exception $e) {
return view('plugin-namespace::dashboard.index', [
'data' => collect(),
'error' => $e->getMessage()
]);
}
}6. Class Autoloading Issues
Problem: Classes not found during plugin loading
ERROR: Class "PluginNamespace\Controllers\Controller" not foundSolutions:
- ✅ Manual Includes: Use
require_oncein route closures - ✅ Constructor Injection: Pass
$pluginPathto controller constructors - ✅ Service Loading: Manually include services and models in controllers
Example:
// In route closure
require_once $pluginPath . '/Controllers/YourController.php';
$controller = new \YourPluginNamespace\Controllers\YourController($pluginPath);
// In controller constructor
public function __construct($pluginPath = null)
{
if ($pluginPath) {
require_once $pluginPath . '/Services/YourService.php';
require_once $pluginPath . '/Models/YourModel.php';
}
}🔧 Debugging Checklist
Before Plugin Activation:
- ✅ Check plugin directory name matches class name
- ✅ Verify main plugin file is named correctly
- ✅ Ensure class name matches file name
- ✅ Confirm namespace is consistent
- ✅ Validate
plugin.jsonstructure
After Plugin Activation:
- ✅ Check if routes are registered (check logs)
- ✅ Verify view namespace is registered
- ✅ Test simple routes first
- ✅ Check database table creation
- ✅ Validate controller return types
Common Debug Routes:
// Add these debug routes to test plugin functionality
Route::get('/test-simple', function () {
return response()->json(['status' => 'Plugin working!']);
})->name('test-simple');
Route::get('/test-view', function () {
return view('plugin-namespace::dashboard.index', ['data' => []]);
})->name('test-view');📋 Plugin Development Checklist
✅ File Structure:
- [ ] Plugin directory with consistent naming
- [ ] Main plugin class with correct naming
- [ ]
plugin.jsonwith matching metadata - [ ] Controllers, Services, Models directories
- [ ] Database migrations directory
- [ ] Views directory with required files
✅ Plugin Class:
- [ ] Extends
Sobberrc\Waravel\Plugins\Plugin - [ ] Public
registerRoutes()method - [ ] Public
registerViews()method - [ ] Proper
initializePlugin()implementation - [ ] Lifecycle methods (
onInstall,onActivate, etc.)
✅ Routes:
- [ ] Use route closures with manual includes
- [ ] Pass
$pluginPathto controllers - [ ] Avoid conflicts with Waravel routes
- [ ] Test routes individually
✅ Views:
- [ ] Required view files (index, details, settings)
- [ ] Correct view namespace registration
- [ ] Proper view namespace in controller returns
- [ ] Error handling in views
✅ Database:
- [ ] Correct table names (use
waravel_prefix) - [ ] Table creation on activation
- [ ] Fallback table creation in controllers
- [ ] Proper model relationships
✅ Controllers:
- [ ] Manual class includes
- [ ] Consistent return types
- [ ] Error handling with try-catch
- [ ] Proper validation
🚀 Quick Start Template
Use this template to avoid common issues:
<?php
namespace YourPluginNamespace;
use Sobberrc\Waravel\Plugins\Plugin;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\View;
class YourPluginNamePlugin extends Plugin
{
protected function initializePlugin(): void
{
$this->name = 'Your Plugin Name';
$this->pluginNamespace = 'YourPluginNamespace';
// ... other properties
}
public function boot(): void
{
// Plugin boot logic - routes and views are registered by Waravel service provider
}
public function registerRoutes(): void
{
$pluginPath = $this->pluginPath;
Route::middleware(['web', 'auth:waravel'])
->prefix('waravel/plugins/your-plugin')
->name('waravel.plugins.your-plugin.')
->group(function () use ($pluginPath) {
Route::get('/', function () use ($pluginPath) {
require_once $pluginPath . '/Controllers/YourController.php';
$controller = new \YourPluginNamespace\Controllers\YourController($pluginPath);
return $controller->index();
})->name('index');
});
}
public function registerViews(): void
{
View::addNamespace('your-plugin-namespace', $this->pluginPath . '/resources/views');
}
protected function onActivate(): void
{
$this->createTables();
}
private function createTables(): void
{
// Create your plugin tables here
}
}📝 Example Plugin
Here's a complete example of a simple plugin:
plugin.json
{
"name": "Task Manager",
"version": "1.0.0",
"description": "Simple task management plugin",
"author": "Your Name",
"author_email": "[email protected]",
"plugin_url": "https://yourwebsite.com/task-manager",
"plugin_namespace": "TaskManager",
"dependencies": [],
"permissions": ["manage_tasks"],
"settings": {
"max_tasks": {
"type": "text",
"label": "Maximum Tasks",
"description": "Maximum number of tasks allowed",
"default": "100"
},
"auto_archive": {
"type": "boolean",
"label": "Auto Archive",
"description": "Automatically archive completed tasks",
"default": true
}
}
}TaskManagerPlugin.php
<?php
namespace TaskManager;
use Sobberrc\Waravel\Plugins\Plugin;
use Illuminate\Support\Facades\Route;
class TaskManagerPlugin extends Plugin
{
protected function initializePlugin(): void
{
$this->name = 'Task Manager';
$this->version = '1.0.0';
$this->description = 'Simple task management plugin';
$this->author = 'Your Name';
$this->authorEmail = '[email protected]';
$this->pluginUrl = 'https://yourwebsite.com/task-manager';
$this->pluginNamespace = 'TaskManager';
$this->dependencies = [];
$this->permissions = ['manage_tasks'];
}
public function boot(): void
{
$this->registerRoutes();
$this->registerViews();
}
protected function registerRoutes(): void
{
Route::middleware(['web', 'auth:waravel'])
->prefix('waravel/plugins/task-manager')
->name('waravel.plugins.task-manager.')
->group(function () {
Route::get('/', [TaskController::class, 'index'])->name('index');
Route::get('/details', [TaskController::class, 'details'])->name('details');
Route::get('/settings', [TaskController::class, 'settings'])->name('settings');
Route::post('/settings', [TaskController::class, 'updateSettings'])->name('settings.update');
});
}
protected function registerViews(): void
{
View::addNamespace('task-manager', __DIR__ . '/resources/views');
}
}🚀 Getting Started
- Create Plugin Directory:
plugins/YourPluginName/ - Add Required Files:
plugin.json, main plugin class - Create Views: Main, details, and settings views
- Implement Controllers: Handle HTTP requests
- Add Database Support: Migrations and models
- Test Your Plugin: Ensure all functionality works
- Document Your Plugin: Add comprehensive README
📚 Additional Resources
Happy Plugin Development! 🎉
For support and questions, please refer to the Waravel documentation or contact the development team.