A powerful, developer-friendly extension framework for Pterodactyl Panel
| Feature | Blueprint | Reshaped |
|---|---|---|
| Manifest format | Custom YAML (flat) | Rich YAML with full schema validation |
| CLI | Bash script | TypeScript CLI with autocomplete |
| Validation | Basic | Zod schema + file existence checks |
| PHP API | Global function calls | Injectable ExtensionLibrary class |
| Data storage | Key-value (string only) | Typed key-value (bool, int, float, JSON) |
| Per-server data | ❌ | ✅ |
| Per-user data | ❌ | ✅ |
| Caching API | ❌ | ✅ |
| Events/listeners | ❌ | ✅ |
| Notifications API | Basic | User notifications + flash messages |
| Permission system | ❌ | ✅ (extends Pterodactyl perms) |
| React hook system | Slot files | <ReshapedSlot> component + registry |
| Error boundaries | ❌ | ✅ Crashes isolated per extension |
| Dev panel | ❌ | ✅ Visual debug overlay |
| Extension settings UI | ❌ (manual) | ✅ Auto-generated from manifest |
| Rollback on failure | ❌ | ✅ Automatic backup + restore |
| Audit logging | ❌ | ✅ |
| Dev mode (symlink) | ❌ | ✅ reshaped install --dev |
- Pterodactyl Panel 1.11+
- PHP 8.1+
- Node.js 20+
- Composer
# In your Pterodactyl root
composer require reshapedframework/framework
# Add to config/app.php providers array:
# Pterodactyl\ReshapedFramework\Providers\ReshapedServiceProvider::class
# Run migrations
php artisan migrate
# Install the CLI globally
npm install -g reshaped-clireshaped init my-extension
# or with a template:
reshaped init my-extension --template fullAvailable templates:
basic— Simple admin pageadmin— Full admin panel with auto-generated settings UIclient— Client panel tabs and hooksapi— API-only, no frontendfull— Everything: admin + client + API + migrations
my-extension/
├── reshaped.yml # Manifest (schema-validated)
├── README.md
├── src/
│ ├── php/
│ │ ├── Controller.php
│ │ └── ServiceProvider.php
│ └── hooks/ # React components injected into panel slots
│ └── AfterTerminal.tsx
├── views/
│ ├── admin/
│ │ └── index.blade.php
│ └── client/
│ └── index.blade.php
├── routes/
│ ├── api.php
│ ├── admin.php
│ └── client.php
├── database/
│ └── migrations/
│ └── 2024_01_01_create_my_extension_table.php
├── config/
│ └── my-extension.php
├── assets/
│ └── logo.png
└── scripts/
├── install.sh
└── uninstall.sh
meta:
id: my-extension
name: My Extension
version: 1.0.0
description: Does something awesome
author: yourusername
license: MIT
compat:
reshaped: ">=1.0.0"
pterodactyl: ">=1.11.0"
provides:
admin:
views: [index.blade.php]
nav:
- label: My Extension
icon: fas fa-puzzle-piece
route: my-extension
api: routes/api.php
migrations: true
hooks:
Server/Terminal/AfterContent:
component: hooks/AfterTerminal
priority: 10
settings:
- key: enabled
label: Enable Extension
type: boolean
default: true
- key: api_url
label: API URL
type: url
validation: "required|url"
permissions:
- key: my-extension.use
description: Use the extension
default: allow<?php
use Pterodactyl\ReshapedFramework\Libraries\ExtensionLibrary;
class MyController extends Controller
{
public function __construct(private ExtensionLibrary $reshaped) {}
public function index()
{
// Settings
$enabled = $this->reshaped->setting('my-extension', 'enabled', true);
$this->reshaped->setSetting('my-extension', 'api_url', 'https://example.com');
// Per-server data
$data = $this->reshaped->serverData('my-extension', $server->id, 'last_run');
$this->reshaped->setServerData('my-extension', $server->id, 'last_run', now());
// Per-user data
$pref = $this->reshaped->userData('my-extension', $user->id, 'preference');
// Caching
$result = $this->reshaped->remember('my-extension', 'expensive_query', 3600, function() {
return DB::table('something')->get();
});
// Notifications
$this->reshaped->notify('Settings saved!', 'success');
$this->reshaped->notifyUser($user->id, 'Alert', 'Something happened', 'warning');
// Events
$this->reshaped->fire('my-extension', 'data.updated', ['key' => 'value']);
// Check other extensions
if ($this->reshaped->isInstalled('other-extension')) {
// Cross-extension compatibility
}
// Logging
$this->reshaped->log('my-extension', 'User performed action', 'info', [
'user_id' => $user->id,
]);
}
}Inject components into any panel slot:
// src/hooks/AfterTerminal.tsx
import React from 'react';
import { useStoreState } from 'easy-peasy';
import { useExtensionSettings } from '@/reshaped/hooks';
export default function AfterTerminal() {
const server = useStoreState(state => state.server.data);
const settings = useExtensionSettings('my-extension');
if (!settings?.enabled || !server) return null;
return (
<div className="mt-4 p-4 bg-gray-700 rounded">
<h3 className="text-white">My Extension</h3>
<p className="text-gray-400">Server: {server.name}</p>
</div>
);
}Add the slot to your panel's Blade/React where you want injections:
import { ReshapedSlot } from '@/reshaped/hooks';
// In ServerConsoleContainer.tsx:
<ReshapedSlot name="Server/Terminal/AfterContent" serverId={server.id} />Available slots: see docs/slots.md
Extensions with a settings array in their manifest automatically get a settings page at /admin/extensions/{id}. No extra code required — just include this in your admin view:
@include('reshaped::admin.settings', ['extension' => 'my-extension'])# Development mode (symlinks, no copy)
reshaped install --dev /path/to/my-extension
# Validate before install
reshaped validate /path/to/my-extension
# Build with hot reload
reshaped build --watch
# List installed
reshaped list
# Remove
reshaped remove my-extensionreshaped install <file.rsx> Install an extension
reshaped install --dev <dir> Install in dev mode
reshaped remove <id> Remove an extension
reshaped init [dir] Scaffold a new extension
reshaped build Build panel assets
reshaped build --watch Build with hot reload
reshaped dev Dev mode with HMR
reshaped export <id> Package to .rsx file
reshaped list List installed extensions
reshaped info <id> Show extension details
reshaped validate <dir> Validate extension
reshaped debug check Check dependencies
reshaped debug logs View debug log
reshaped upgrade Upgrade Reshaped
@reshapedSlot('Server/Terminal/AfterContent')
@reshapedAsset('my-extension', 'script.js')
@reshapedSetting('my-extension', 'api_url')
@ifExtension('my-extension') ... @endIfExtension
@reshapedPermission('my-extension', 'use') ... @endReshapedPermissionSee docs/slots.md for the full list of available injection points.
Licesend under MIT. Check LICENSE for more information