Extensions

Building Extensions

Create an extension with API endpoints, settings, pages, and additions to the Panel's interface.

Introduction

An extension has a PHP backend that runs inside the Panel like a Laravel package, a React frontend that the Panel loads in the browser, or both. This guide builds an example extension, Server Notes, that adds a Notes page to every server, shows the notes above the console, stores them in its own database table, and has a setting for the maximum note length.

Read Extensions first to learn how extensions are installed and managed.

The guide has five parts:

  1. Building Extensions (this page): create the extension and write its manifest.
  2. Backend: the service provider, routes, database, settings, and background work.
  3. Frontend: slots, screens, tabs, table columns, and component replacements.
  4. Data and Styling: Panel data, your API, components, and translations.
  5. Testing and Packaging: tests, builds, packages, and security.

Requirements

You need:

  • A Pterodactyl 2.0 source checkout, set up as a development Panel.
  • PHP 8.3 or newer and Composer.
  • Node.js 22.12 or newer.

Extensions created from a Panel checkout use its local @pterodactyl/sdk package. After installing the Panel's frontend dependencies, build the SDK test runtime from the Panel root:

npm run sdk:testing

Extension tests use this runtime. Rebuild it when you change the SDK source.

Creating an Extension

From your Panel checkout, run p:extension:make:

php artisan p:extension:make server-notes --name="Server Notes" --description="Keep shared notes on each server."

This creates the extension in the Panel's extensions directory. Install its frontend dependencies and build it:

cd extensions/server-notes
npm install
npm run typecheck
npm test
npm run build
cd ../..

Then install and enable it in your development Panel:

php artisan p:extension:install extensions/server-notes --enable

p:extension:make accepts these options:

OptionDescription
--nameDisplay name. Defaults to the ID in title case.
--descriptionShort description.
--authorAuthor's name.
--prefixTailwind prefix for the extension's classes, 2 to 12 lowercase letters. Defaults to one derived from the ID.
--no-uiCreate a backend-only extension, with no frontend.
--outCreate the extension in a different directory.
--forceReplace an existing directory.

Extension IDs

The ID names your extension everywhere: its directory, URLs, and settings. It must start with a lowercase letter, contain only lowercase letters, numbers, and hyphens, and be at most 48 characters. pterodactyl, panel, and core are reserved.

The extension's directory must have the same name as its ID.

Directory Structure

With the files from this guide added, Server Notes looks like this:

server-notes/
├── .github/workflows/ci.yml
├── .gitignore
├── extension.json
├── README.md
├── database/
│   └── migrations/
│       └── 2026_09_29_000000_create_ext_server_notes_notes_table.php
├── routes/
│   └── server.php
├── scripts/
│   └── externalize-api.mjs
├── src/
│   ├── ServerNotesProvider.php
│   ├── Http/Controllers/NoteController.php
│   ├── Models/Note.php
│   └── client/
│       ├── index.tsx
│       ├── styles.css
│       ├── api.ts
│       ├── config.ts
│       ├── NotePreview.tsx
│       └── screens/
│           ├── NotesScreen.tsx
│           └── NotesScreen.spec.tsx
├── openapi-ts.config.ts
├── package.json
├── postcss.config.mjs
├── tsconfig.json
├── vite.config.mjs
└── vitest.config.mjs

The command also creates sample client routes in routes/client.php, a StatusController, and a ServerScreen with a test. Server Notes replaces them with the files above. openapi-ts.config.ts and scripts/externalize-api.mjs generate API clients, and the GitHub Actions workflow runs type checks, tests, and builds. With --no-ui, the command creates only the manifest, provider, sample controller and routes, migrations directory, .gitignore, and README.md.

The Manifest

The extension.json file in the extension's root tells the Panel what the extension is and how to load it:

{
    "$schema": "./node_modules/@pterodactyl/sdk/manifest.schema.json",
    "id": "server-notes",
    "name": "Server Notes",
    "version": "1.0.0",
    "requires": {
        "panel": "^2.0.0-dev",
        "sdk": "^2.0.0-beta.4",
        "php": "^8.3"
    },
    "description": "Keep shared notes on each server.",
    "provider": "ServerNotes\\ServerNotesProvider",
    "autoload": {
        "ServerNotes\\": "src"
    },
    "ui": {
        "entry": "dist/client.js",
        "mode": "native",
        "prefix": "sn",
        "screens": [
            {
                "id": "notes",
                "area": "server",
                "path": "notes",
                "nav": { "label": "Notes" }
            }
        ]
    }
}

The provider and autoload keys load your PHP code. The ui key loads your frontend bundle and declares its pages. ui.prefix is the Tailwind prefix for your extension's classes, derived from the ID unless you pass --prefix. The Panel rejects a build whose Tailwind utilities or theme variables do not use it. Leave out the keys for any half you do not use. See the Extension Reference for every key.

Icon

Administrators see each extension as a card on the Extensions page. Without an icon, the card shows the extension's initials. Set icon to a Lucide icon name:

"icon": "notebook-pen"

Or ship an image in the extension and give its path:

"icon": "icon.png"

The image must be a PNG, JPEG, or WebP file of at most 512 KB and 2048 pixels on each side. Use a square image of at least 128 pixels. p:extension:pack includes it in the package wherever it is, and p:extension:doctor warns if the Panel cannot show it. See Extension Icons for the full rules.

Compatibility

Declare the versions your extension supports with requires.panel, requires.sdk, and requires.php. Constraints use Composer syntax, such as ^2.0.0-beta.4 or >=8.3 <9.0. The values above match the development Panel and its SDK.

To depend on another extension, declare its ID and version constraint under requires.extensions:

"requires": {
    "extensions": {
        "shared-notes": "^1.0"
    }
}

The required extension must be enabled before yours can be. The Panel loads dependencies first, rejects dependency cycles, and skips extensions with unmet requirements. If a dependency fails, its dependents do not load either.

The $schema entry enables manifest completion in editors that support JSON Schema. The Panel validates the manifest when the package is installed and enabled.

Check a manifest

Break it
acceptedThe Panel reads this manifest without complaint.

Installed versions

Enabling checks requires.panel, requires.sdk and requires.php against these. Canary builds report Panel 2.0.0-dev; this Panel's SDK is 2.0.0-beta.4.

40 of 40 checks pass. Groups with a problem open on their own.

Edit on GitHub

Last updated on

On this page