Skip to content

Notur PHP API Reference

This document covers all PHP contracts, interfaces, services, and systems available to extension developers.

Table of Contents


Contracts (Interfaces)

All contracts live in the Notur\Contracts namespace. Your extension's entrypoint class must implement ExtensionInterface (either directly or by extending NoturExtension). The remaining interfaces are opt-in and enable specific capabilities.

Abstract base class that auto-reads metadata from extension.yaml. This is the recommended way to create extensions — it eliminates boilerplate by resolving getId(), getName(), getVersion(), and getBasePath() from the manifest automatically.

php
namespace Notur\Support;

abstract class NoturExtension implements ExtensionInterface
{
    public function getId(): string;       // Auto-resolved from extension.yaml
    public function getName(): string;     // Auto-resolved from extension.yaml
    public function getVersion(): string;  // Auto-resolved from extension.yaml
    public function getBasePath(): string; // Auto-resolved by walking up to find extension.yaml
    public function register(): void;      // No-op by default
    public function boot(): void;          // No-op by default
}

Example:

php
use Notur\Support\NoturExtension;
use Notur\Contracts\HasRoutes;

class MyExtension extends NoturExtension implements HasRoutes
{
    public function register(): void
    {
        // Bind services into the container, set config defaults, etc.
    }

    public function getRouteFiles(): array
    {
        return ['api-client' => 'src/routes/api-client.php'];
    }
}

ExtensionInterface (base contract)

The base contract that every extension must implement. If you extend NoturExtension, this is already implemented for you.

php
namespace Notur\Contracts;

interface ExtensionInterface
{
    public function getId(): string;
    public function getName(): string;
    public function getVersion(): string;
    public function register(): void;
    public function boot(): void;
    public function getBasePath(): string;
}

Direct implementation (use NoturExtension instead for less boilerplate):

php
use Notur\Contracts\ExtensionInterface;

class MyExtension implements ExtensionInterface
{
    public function getId(): string { return 'acme/my-extension'; }
    public function getName(): string { return 'My Extension'; }
    public function getVersion(): string { return '1.0.0'; }
    public function getBasePath(): string { return __DIR__ . '/..'; }

    public function register(): void
    {
        // Bind services into the container, set config defaults, etc.
    }

    public function boot(): void
    {
        // Post-registration logic: publish assets, register view composers, etc.
    }
}

HasRoutes

Opt into HTTP route registration.

php
namespace Notur\Contracts;

interface HasRoutes
{
    /**
     * Return an array of route file paths keyed by group name.
     *
     * Supported groups: "api-client", "admin", "web"
     *
     * @return array<string, string>
     */
    public function getRouteFiles(): array;
}

Example:

php
public function getRouteFiles(): array
{
    return [
        'api-client' => 'src/routes/api-client.php',
        'admin'      => 'src/routes/admin.php',
        'web'        => 'src/routes/web.php',
    ];
}

Route file paths are relative to the extension's base directory.

HasMigrations

Opt into database migration management.

php
namespace Notur\Contracts;

interface HasMigrations
{
    /**
     * Return the path to the extension's migrations directory.
     */
    public function getMigrationsPath(): string;
}

Example:

php
public function getMigrationsPath(): string
{
    return $this->getBasePath() . '/database/migrations';
}

Migrations are tracked per-extension in the notur_migrations table, separate from Laravel's own migration table.

HasCommands

Register Artisan console commands.

php
namespace Notur\Contracts;

interface HasCommands
{
    /**
     * Return an array of artisan command class names.
     *
     * @return array<class-string>
     */
    public function getCommands(): array;
}

HasHealthChecks

Expose health check results for the admin UI.

php
namespace Notur\Contracts;

interface HasHealthChecks
{
    /**
     * Return health check results for this extension.
     *
     * Each entry should include:
     * - id (string)
     * - status: ok|warning|error|unknown
     * - message (optional)
     * - details (optional)
     *
     * @return array<int|string, array<string, mixed>>
     */
    public function getHealthChecks(): array;
}

Example:

php
public function getHealthChecks(): array
{
    return [
        [
            'id' => 'db',
            'status' => 'ok',
            'message' => 'Database reachable',
        ],
    ];
}

HasMiddleware

Register HTTP middleware into middleware groups.

php
namespace Notur\Contracts;

interface HasMiddleware
{
    /**
     * Return middleware class names keyed by middleware group.
     *
     * @return array<string, array<class-string>>
     */
    public function getMiddleware(): array;
}

Example:

php
public function getMiddleware(): array
{
    return [
        'web' => [
            \Acme\Analytics\Http\Middleware\TrackPageView::class,
        ],
        'api' => [
            \Acme\Analytics\Http\Middleware\RateLimit::class,
        ],
    ];
}

HasEventListeners

Register event listeners.

php
namespace Notur\Contracts;

interface HasEventListeners
{
    /**
     * Return event-to-listener mappings.
     *
     * @return array<class-string, array<class-string>>
     */
    public function getEventListeners(): array;
}

Example:

php
public function getEventListeners(): array
{
    return [
        \Pterodactyl\Events\Server\Created::class => [
            \Acme\Analytics\Listeners\InitializeServerAnalytics::class,
        ],
        \Pterodactyl\Events\Server\Deleted::class => [
            \Acme\Analytics\Listeners\CleanupServerAnalytics::class,
        ],
    ];
}

HasBladeViews

Register a Blade view namespace for server-side rendering.

php
namespace Notur\Contracts;

interface HasBladeViews
{
    /**
     * Return the path to the extension's views directory.
     */
    public function getViewsPath(): string;

    /**
     * Return the view namespace (e.g., "acme-analytics").
     */
    public function getViewNamespace(): string;
}

Example:

php
public function getViewsPath(): string
{
    return $this->getBasePath() . '/resources/views';
}

public function getViewNamespace(): string
{
    return 'acme-analytics';
}

Views are then usable as @include('acme-analytics::dashboard').

HasFrontendSlots (deprecated)

Deprecated: Register frontend slots via createExtension() in your frontend code instead. This interface will continue to work but emits a deprecation warning.

Declare frontend slot metadata. This data is passed to the bridge runtime for rendering.

php
namespace Notur\Contracts;

interface HasFrontendSlots
{
    /**
     * Return frontend slot registrations as defined in extension.yaml.
     *
     * @return array<string, array<string, mixed>>
     */
    public function getFrontendSlots(): array;
}

Example:

php
public function getFrontendSlots(): array
{
    return [
        'server.subnav' => [
            'label' => 'Analytics',
            'icon' => 'chart-bar',
            'permission' => 'analytics.view',
        ],
        'dashboard.widgets' => [
            'component' => 'AnalyticsWidget',
            'order' => 10,
        ],
    ];
}

Route Groups

Extension routes are automatically scoped under namespaced URL prefixes. The route file receives a standard Laravel Router context.

GroupURL PrefixDefault MiddlewarePurpose
api-client/api/client/notur/{extension-id}/client-apiClient-facing API endpoints
admin/admin/notur/{extension-id}/web, adminAdmin panel endpoints
web/notur/{extension-id}/webGeneral web routes

The {extension-id} is derived from your extension's ID with slashes preserved (e.g., acme/server-analytics).

Route file example (src/routes/api-client.php):

php
<?php

use Illuminate\Support\Facades\Route;
use Acme\Analytics\Http\Controllers\StatsController;

// URL: /api/client/notur/acme/server-analytics/stats
Route::get('/stats', [StatsController::class, 'index']);

// URL: /api/client/notur/acme/server-analytics/export
Route::get('/export', [StatsController::class, 'export']);

// URL: /api/client/notur/acme/server-analytics/settings
Route::post('/settings', [StatsController::class, 'updateSettings']);

Notur Client Endpoints

Notur exposes a small client API for frontend tooling:

  • GET /api/client/notur/slots — slot registrations for all enabled extensions
  • GET /api/client/notur/extensions — extension metadata for all enabled extensions
  • GET /api/client/notur/extensions/{extension-id}/settings — public settings (fields with public: true)
  • GET /api/client/notur/config — Notur config for the bridge runtime

Permission System

Extensions can define custom permissions in their extension.yaml:

yaml
backend:
  permissions:
    - "analytics.view"
    - "analytics.export"
    - "analytics.admin"

These permissions are enforced via the PermissionBroker middleware, which runs on all extension route groups. On the frontend, the usePermission hook checks permissions against the current server context.

Checking permissions in a controller:

php
// Permissions are automatically enforced by the middleware based on the
// route and the extension's declared permissions. You can also check manually:
if (!$request->user()->can('analytics.export')) {
    abort(403);
}

Checking permissions on the frontend:

tsx
import { usePermission } from '@notur/sdk';

const AnalyticsExport: React.FC = () => {
    const canExport = usePermission('analytics.export');

    if (!canExport) return null;
    return <button>Export Data</button>;
};

Migration System

Notur has its own migration system separate from Laravel's. Extension migrations are tracked in the notur_migrations table to enable per-extension migrate and rollback.

How it works

  1. When an extension is installed, its migrations directory (from HasMigrations::getMigrationsPath()) is scanned.
  2. Each migration file is run and recorded in notur_migrations with the extension ID.
  3. When an extension is removed, its migrations are rolled back in reverse order.
  4. When an extension is updated, any new migration files are run.

Migration file format

Use standard Laravel migration syntax:

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('ext_analytics_data', function (Blueprint $table) {
            $table->id();
            $table->string('server_uuid');
            $table->json('metrics');
            $table->timestamps();
        });
    }

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

Naming convention: Prefix your tables with ext_ or your vendor name to avoid collisions.

Notur's own tables

Notur creates 4 tables during installation:

TablePurpose
notur_extensionsInstalled extension records (ID, name, version, enabled status)
notur_migrationsPer-extension migration tracking
notur_settingsPer-extension key-value settings store
notur_activity_logsExtension activity audit trail

Events

Notur dispatches events during extension lifecycle operations. You can listen for these in other extensions or in custom Laravel listeners.

Event ClassWhen DispatchedPayload
Notur\Events\ExtensionInstalledAfter an extension is installedstring $extensionId
Notur\Events\ExtensionUpdatedAfter an extension is updatedstring $extensionId, string $fromVersion, string $toVersion
Notur\Events\ExtensionEnabledAfter an extension is enabledstring $extensionId
Notur\Events\ExtensionDisabledAfter an extension is disabledstring $extensionId
Notur\Events\ExtensionRemovedAfter an extension is removedstring $extensionId

Listening for events:

php
// In your extension's getEventListeners():
public function getEventListeners(): array
{
    return [
        \Notur\Events\ExtensionEnabled::class => [
            \Acme\MyExtension\Listeners\OnExtensionEnabled::class,
        ],
    ];
}

Models

InstalledExtension

The Notur\Models\InstalledExtension Eloquent model represents a row in the notur_extensions table.

Key attributes:

AttributeTypeDescription
extension_idstringThe extension ID (vendor/name)
namestringHuman-readable name
versionstringInstalled version
enabledboolWhether the extension is active

ExtensionActivity

The Notur\Models\ExtensionActivity model represents entries in the notur_activity_logs table.

Key attributes:

AttributeTypeDescription
extension_idstringExtension ID
actionstringAction name (installed, updated, enabled, disabled, removed)
summarystringHuman-readable summary
contextarrayOptional structured context

Configuration

The Notur config file (config/notur.php) exposes the following keys:

KeyTypeDefaultDescription
versionstring'1.2.3'Notur framework version
extensions_pathstring'notur/extensions'Extension storage directory (relative to panel root)
require_signaturesboolfalseRequire Ed25519 signatures on .notur archives
registry_urlstring'https://raw.githubusercontent.com/notur/registry/main'Remote registry base URL
registry_cache_pathstringstorage_path('notur/registry-cache.json')Local registry cache file
registry_cache_ttlint3600Registry cache TTL in seconds (0 disables expiry)
public_keystringenv('NOTUR_PUBLIC_KEY', '')Ed25519 public key for signature verification

CLI Commands

All commands are prefixed with notur: in Artisan.

CommandSignatureDescription
notur:installnotur:install {extension} [--force] [--no-migrate]Install from registry or .notur file
notur:removenotur:remove {extension} [--keep-data]Remove an extension
notur:enablenotur:enable {extension}Enable a disabled extension
notur:disablenotur:disable {extension}Disable an extension without removing files
notur:listnotur:list [--enabled] [--disabled]List installed extensions
notur:updatenotur:update {extension?} [--check]Update one or all extensions
notur:statusnotur:status [--json] [--health] [--extensions]Display system status dashboard
notur:new`notur:new {id} [--path=] [--preset=] [--with-api-routes--no-api-routes] [--with-admin-routes
notur:validatenotur:validate {path?} [--strict]Validate an extension manifest and settings schema
notur:devnotur:dev {path} [--link] [--watch] [--watch-bridge]Link a local extension for development
notur:exportnotur:export {path?} [--output=] [--sign]Export extension as .notur archive
notur:buildnotur:buildBuild extension frontend assets
notur:keygennotur:keygenGenerate an Ed25519 keypair for extension signing
notur:registry:syncnotur:registry:sync [--search=] [--force]Sync or search the extension registry
notur:registry:statusnotur:registry:status [--json]Show registry cache status
notur:uninstallnotur:uninstall [--confirm]Completely remove Notur from the panel

Preset Definitions

For notur:new:

  • standard: frontend + API routes (default)
  • backend: API routes only
  • full: frontend + API routes + admin UI + migrations + tests
  • minimal: backend-only scaffolding with no routes or frontend

Released under the MIT License.