Xgenious/ docs

Routes, Views & Settings

Routes

Place in routes/web.php or routes/api.php. Auto-loaded by the framework.

Web routes (with web middleware — auth, CSRF, session):

Route::middleware('auth')->group(function () {
    Route::post('my-plugin/action', [MyController::class, 'handle'])
        ->name('my-plugin.action');
});

API routes (with api middleware — no CSRF, no session):

Route::post('my-plugin/webhook', [MyWebhookController::class, 'handle'])
    ->name('my-plugin.webhook');

Admin routes:

Route::group([
    'prefix' => 'admin/my-plugin',
    'as' => 'admin.my-plugin.',
    'middleware' => ['auth:admin', 'setlang'],
], function () {
    Route::get('/', [MyAdminController::class, 'index'])
        ->name('index')->permission('my-plugin-manage');
});

Views

Place Blade files in views/. Access as view('my-plugin::filename').

// From a slot renderer
$this->ctx->slots->register('page.banner', 'my-plugin.banner', function (array $ctx): string {
    ob_start();
    require __DIR__ . '/../views/banner.php';
    return ob_get_clean();
});

Database Migrations

Place in database/migrations/. Auto-loaded by artisan migrate.

// database/migrations/2026_01_01_000001_create_my_tables.php
return new class extends Migration {
    public function up(): void
    {
        Schema::create('my_plugin_data', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            $table->string('value');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('my_plugin_data');
    }
};

Rules:

  • Prefix table names with plugin slug to avoid collisions
  • Always provide down() for rollback
  • Migrations run in the core database

Settings Storage

// Read
$value = get_static_option('my_plugin_api_key', 'default');

// Write
update_static_option('my_plugin_api_key', $newValue);

Option B: Config File

Place config/my-plugin.php, access via config('plugins.my-plugin.key').

Option C: Plugin-Owned Tables

Create your own tables via migrations. Use Eloquent models within your plugin namespace.

Admin Menu Registration

{
    "xilancerMetaData": {
        "admin_settings": {
            "menu_item": [
                {
                    "id": "my-plugin-menu",
                    "label": "My Plugin",
                    "route": "admin.my-plugin.index",
                    "icon": "fa-solid fa-star",
                    "permissions": "my-plugin-manage"
                }
            ]
        }
    }
}

Via Registry (Programmatic)

$this->ctx->registry->register('admin.menu', 'my-plugin', [
    'label' => 'My Plugin',
    'route' => '/admin/my-plugin',
    'icon' => 'fa-solid fa-star',
    'permissions' => 'my-plugin-manage',
]);

Complete Example: Discount Coupon Plugin

plugins/coupons/
├── plugin.json
├── src/
│   ├── CouponsPlugin.php
│   ├── Coupon.php
│   ├── CouponRedemption.php
│   ├── CouponStore.php
│   ├── DbCouponStore.php
│   └── Http/
│       └── CouponApplyController.php
├── routes/web.php
├── database/migrations/
└── views/

Entry class:

final class CouponsPlugin implements PluginInterface
{
    public function __construct(private PluginContext $ctx) {}

    public function boot(): void
    {
        // Hook: modify checkout price when coupon applied
        $this->ctx->hooks->addFilter('checkout.price.calculate',
            [CouponApplyController::class, 'applyDiscount']
        );

        // Slot: inject coupon field into checkout
        $this->ctx->slots->register('checkout.coupon_field', 'coupons.field',
            fn(array $ctx): string => view('coupons::coupon-field', $ctx)->render()
        );

        // Admin menu
        $this->ctx->registry->register('admin.menu', 'coupons', [
            'label' => 'Coupons',
            'route' => '/admin/coupons',
            'icon' => 'fa-solid fa-ticket',
            'permissions' => 'coupons-manage',
        ]);
    }
}

Last updated: September 2026

Still stuck?
Our support team is ready to help you get set up.
Get support