Extension Reference
The manifest, commands, backend helpers, SDK, and slots available to extensions.
Introduction
This page lists everything available to extensions. For a guided introduction, see Building Extensions.
Manifest
extension.json supports these keys:
| Key | Required | Description |
|---|---|---|
id | Yes | The extension's ID. Lowercase letters, numbers, and hyphens, starting with a letter, up to 48 characters. Must match the directory name. pterodactyl, panel, and core are reserved. |
name | Yes | The display name. |
version | Yes | The version, up to 64 characters. |
$schema | No | Editor schema, usually ./node_modules/@pterodactyl/sdk/manifest.schema.json in a frontend package. |
requires.panel | No | Supported Panel versions, using Composer constraints. |
requires.sdk | With component replacements | Supported SDK versions, using Composer constraints. |
requires.php | No | Supported PHP versions, using Composer constraints. |
requires.extensions | No | Required extension IDs mapped to version constraints. Each dependency must be enabled. |
description | No | Short description shown in the admin area. |
author | No | Author shown in the admin area. |
icon | No | Icon shown for the extension on the admin Extensions page, up to 255 characters. Either a Lucide icon name in kebab-case, such as life-buoy, or the relative path of a PNG, JPEG, or WebP image inside the extension, such as icon.png. See Extension Icons. Without an icon, the page shows the extension's initials. |
provider | No | Fully qualified class name of the service provider. Must extend Pterodactyl\Extensions\ExtensionProvider. |
autoload | No | PSR-4 map of namespace prefixes to directories inside the extension, such as {"ServerNotes\\": "src"}. |
routes.root | No | Top-level URL prefixes for registerRootRoutes, up to 8, such as ["go"]. Each is a lowercase slug of up to 48 characters. Prefixes the Panel uses or another enabled extension claims are rejected. |
ui.entry | With ui | The frontend bundle. Must be dist/client.js. |
ui.mode | No | Must be native (the default). |
ui.prefix | With Tailwind utilities | The Tailwind prefix for the extension's classes, 2 to 12 lowercase letters, such as sn. Tailwind variants, theme namespaces, and Panel names are reserved. Two enabled extensions cannot share a prefix. Builds whose Tailwind utilities or theme variables lack the prefix are rejected. |
ui.screens | No | Pages the extension adds, up to 64. |
ui.components | No | Supported component names this extension replaces. Names must be unique. |
Each entry in ui.screens supports these keys:
| Key | Required | Description |
|---|---|---|
id | Yes | The screen's ID, used with screens.register. Lowercase letters, numbers, and hyphens, starting with a letter. |
area | Yes | server, account, or admin. |
parent | No | An admin detail page: admin.node, admin.server, admin.user, or admin.egg. Only accepted with area: "admin". |
path | Yes | A static first segment, followed by static or named parameter segments, such as notes/$tab. $id is reserved. Paths must not collide with core or extension routes. |
nav.label | No | Navigation tab label, up to 100 characters. Without nav, the page has no tab. |
nav.exact | No | Highlight the navigation item only on an exact path match. |
nav.params | For parameterized navigation | Values for every named parameter in the screen path. |
nav.order | No | Sort order among extension navigation items. Defaults to 0. |
nav.group | No | Group metadata shown as a tooltip, up to 100 characters. |
nav.badge | No | Short label beside the navigation item, up to 32 characters. |
nav.icon | No | Any Lucide icon name in kebab-case, up to 64 characters, such as puzzle, life-buoy, or terminal. A name this Panel does not ship renders the default icon, and p:extension:doctor warns about it. |
permission | No | Only accepted on server screens. The page and navigation item require at least one of the listed subuser permissions. Backend routes must still authorize the request. |
when.eggTags | No | Only accepted on server screens. { "any": [...], "all": [...] }, matched without regard to case against the server's egg tags. all requires every value and any at least one. Each list holds 1 to 32 values. |
when.eggFeatures | No | Like when.eggTags, matched against the server's egg features. |
when.match | No | all (the default) or any. Combines eggTags and eggFeatures, and requires both. |
when.runtime | No | true hides the screen until the visible predicate passed to screens.register returns true. Accepted in any area. |
A screen whose when conditions fail has no navigation item, and its URL shows the not-found page. when must declare an egg rule or "runtime": true.
Screen URLs use these roots, followed by the screen's path:
| Area or parent | URL root |
|---|---|
account | /account |
server | /server/{id} |
admin | /panel |
admin.node | /panel/nodes/{id} |
admin.server | /panel/servers/{id} |
admin.user | /panel/users/{id} |
admin.egg | /panel/eggs/{eggId} |
Extension Icons
The icon key accepts two forms:
"icon": "life-buoy""icon": "resources/icon.png"A value made of lowercase letters, numbers, and single hyphens is a Lucide icon name. A name this Panel does not ship renders the default icon, and p:extension:doctor warns about it.
A value ending in .png, .jpg, .jpeg, or .webp is an image path, relative to the extension's root and using / between directories. No directory or file name may start with a dot, so the path cannot leave the extension. The Panel accepts the image when:
- It is a PNG, JPEG, or WebP image, judged from the file's contents rather than its name. SVG is not supported.
- It is at most 512 KB and at most 2048 pixels on each side.
- It resolves to a file inside the extension. A symlink that points outside the extension is refused.
Use a square image of at least 128 pixels. The Panel shows it at 40 pixels, scaled to fit, on the extension's own background.
The Panel serves the image from the installed extension to administrators who can view extensions, at /api/admin/extensions/{id}/icon, so it shows while the extension is disabled. It does not need to be in dist. The icon_url field of the Admin API's extension list holds the image's URL, including a version that changes with the file's contents. An image that is missing or fails these checks leaves icon_url empty, and the page shows the extension's initials. The manifest stays valid, and p:extension:doctor warns about the image.
Compatibility is checked before activation. Dependencies load first; incompatible packages and dependency cycles are skipped. Disable dependents before disabling or removing a required extension.
Check a manifest
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.
Artisan Commands
| Command | Description |
|---|---|
p:extension:make {id} | Create a new extension. Options: --name, --description, --author, --prefix, --no-ui, --out, --force. |
p:extension:install {path} | Install a .pteroext file, .zip file, or directory. Add --enable to enable it afterwards. |
p:extension:enable {id} | Enable an extension, publish its frontend files, and run its migrations. |
p:extension:disable {id} | Disable an extension, keeping its files and data. |
p:extension:remove {id} | Delete an extension's files and published assets, keeping its tables and settings. |
p:extension:list | List installed extensions and their state. |
p:extension:doctor {path} | Check an unpacked package's manifest, compatibility, autoload directories, and built files without installing it. Warns about icon names this Panel does not ship and icon images it cannot serve. |
p:extension:dev {path} | Build and publish once. Add --enable to enable the package and --watch to publish each successful rebuild. Browser reload requires APP_DEBUG=true. |
p:extension:types {path} | Generate literal extension, screen, permission, and public configuration types. Defaults to src/client/extension-types.ts; change it with --output. Settings and permission definitions load only when the extension is enabled. |
p:extension:pack {path} | Create a runtime .pteroext archive, including the image named by icon. Defaults to {id}-{version}.pteroext in the working directory. Options: --output, --force. |
Backend
Provider Methods
Pterodactyl\Extensions\ExtensionProvider has these protected methods:
| Method | Description |
|---|---|
registerApiRoutes() | Load routes/client.php, routes/server.php, routes/admin.php, and routes/application.php, if they exist. |
loadExtensionMigrations() | Register database/migrations. |
registerSettings($definition) | Register the extension's settings. |
settings() | Return the extension's key-value settings store. |
registerPermissions($description, $keys) | Register permission keys and their descriptions under ext.{id}.{key}. |
registerFields($model, $fields) | Add the fields a Fields class declares to the admin forms of a model. See Form Fields. |
listenToServerOperations($listener) | Listen for an immutable OperationCompleted result; errors are recorded against the extension. |
loadExtensionViews() | Load Blade views from resources/views, as ext-{id}::view. |
loadExtensionTranslations() | Load translations from resources/lang, as ext-{id}::key. |
id() | Return the extension's ID. |
extensionPath(...$parts) | Return an absolute path inside the extension's directory. |
Route Helpers
Each helper loads a route file under a prefix, with the Panel's middleware. registerApiRoutes calls the first four.
| Method | URL prefix | Access |
|---|---|---|
registerClientApiRoutes($path) | /api/client/extensions/{id} | Signed-in users and Client API keys. |
registerServerApiRoutes($path) | /api/client/servers/{server}/extensions/{id} | Users with access to the server. |
registerAdminApiRoutes($path) | /api/admin/extensions/{id} | Root administrators. |
registerApplicationApiRoutes($path) | /api/application/extensions/{id} | Root administrators, including Application API keys. |
registerAuthenticatedWebRoutes($path) | /extensions/{id} | Signed-in users. |
registerWebRoutes($path) | /extensions/{id} | Anyone. Add your own middleware to protect these routes. |
registerRootRoutes($path, $prefix = null) | /{prefix}, for each prefix in routes.root or only $prefix | Anyone, rate limited per client by extensions.root_routes_per_minute (default 120). Undeclared prefixes are refused. |
Route names are prefixed with extensions.{id}., followed by client., server., admin., application., web., or root.{prefix}.. The path /api/admin/extensions/{id}/settings is reserved for the Panel.
Settings
ExtensionSettingDefinition::make($key, $input, $default, $rules) defines one setting, with these methods:
| Method | Description |
|---|---|
label($label) | The form label. Defaults to the input name in title case. |
help($text) | Help text shown below the field. |
field($type, $options = []) | The form control: text, password, number, toggle, or select. For select, pass a list of ['value' => ..., 'label' => ...] options. |
frontend() | Send the value to the frontend bundle as config, readable by every signed-in user. config is empty before sign-in. |
frontendType($type) | Declare and check a public value's type: string, number, boolean, array, object, null, or json. Defaults to json. |
secret() | Encrypt the stored value, use a password control, and return it blank to the admin form. Cannot be combined with frontend(). Empty updates keep the saved value. |
normalizeUsing($callback) | Transform the value when it is read and saved. |
publicUsing($callback) | Transform the value before it is shown, for example to mask it. |
The admin form validates submitted values against their definitions. Updates can be partial. Values must be JSON scalars or arrays, nested at most ten levels deep. A password control alone does not enable encryption.
The store returned by settings() supports:
| Method | Description |
|---|---|
get($key, $default = null) | Read a value, decrypting it when stored as a secret. |
set($key, $value) | Store an ordinary JSON value. |
setSecret($key, $value) | Store an encrypted value. Use it for every write to a managed secret. |
setMany($values) | Store a map of ordinary values in one write. |
setManySecrets($values, $secretKeys) | Store a map of values, encrypting the keys in $secretKeys. |
forget($key) | Delete a value. |
all() | Read all values in this scope. |
getByPrefix($prefix) | Read matching keys, removing the prefix from the returned keys. |
forgetByPrefix($prefix) | Delete matching keys. |
forUser($user) | Return a store scoped to a persisted user. |
forServer($server) | Return a store scoped to a persisted server. |
for($model) | Return a store scoped to a persisted user, server, node, egg, location, mount, or database host. |
fields($model) | Return the store of the form field values the Panel keeps for a model, when your Fields class has no values and save. |
The global scope, each model's scope, and each model's form field values are separate. Deleting a model deletes its scoped settings and field values. ExtensionSettings::preload($stores) loads several stores together. An ExtensionSettingsDefinition also supports forUser and forServer.
Permissions
Register permissions with registerPermissions($description, ['read' => 'Read notes.']). The key becomes ext.{id}.read and appears in the server subuser editor. Use it with $user->can(...), the SDK permission hooks, and a server screen's permission list. A wildcard such as ext.server-notes.* covers only that extension. Server owners and root administrators pass every permission check.
You can also use these core permissions:
| Group | Permissions |
|---|---|
| Console | websocket.connect, control.console, control.start, control.stop, control.restart |
| Subusers | user.create, user.read, user.update, user.delete |
| Files | file.create, file.read, file.read-content, file.update, file.delete, file.archive, file.sftp |
| Backups | backup.create, backup.read, backup.delete, backup.download, backup.restore |
| Network | allocation.read, allocation.create, allocation.update, allocation.delete |
| Startup | startup.read, startup.update, startup.docker-image |
| Databases | database.create, database.read, database.update, database.delete, database.view_password |
| Schedules | schedule.create, schedule.read, schedule.update, schedule.delete |
| Settings | settings.rename, settings.reinstall |
| Activity | activity.read |
Form Fields
registerFields($model, $fields) adds the fields declared by a class extending Pterodactyl\Extensions\Fields to the admin create and edit forms of a model. Register one class for each model. Every method of the class is optional, and each is called through the service container, so it may type-hint the model and any other dependency.
| Method | Called | Returns |
|---|---|---|
rules() | On every save that validates the extension, including creation. | Validation rules keyed by field. They apply to the extension's values alone. |
values($model) | When a resource is returned. | The current values, keyed by field. |
save($model, $values) | Inside the transaction that writes the model, after its row is written. The parameter must be named $values. | Nothing. |
authorize() | Before values are accepted or returned. | Whether the signed-in administrator may see and change the values. |
secrets() | When values are stored and returned. | The fields that hold credentials. They are encrypted and returned as ********. |
attributes(), messages() | During validation. | Field names and messages for validation errors. |
Methods other than values and save also run while a model is created, so their model parameter must be nullable. Implement both values and save, or neither: without them, the Panel stores the values, readable with settings()->fields($model). Registering an unsupported model, a class that does not extend Fields, only one of values and save, or the same model twice stops the extension from loading.
| Model | Admin form | Pages |
|---|---|---|
Pterodactyl\Models\User | admin.user | Create user, user details |
Pterodactyl\Models\Server | admin.server | Create server, server Details tab |
Pterodactyl\Models\Node | admin.node | Create node, node Settings tab |
Pterodactyl\Models\Egg | admin.egg | Create egg, egg Configuration tab |
Pterodactyl\Models\Location | admin.location | New location dialog, location details |
Pterodactyl\Models\Mount | admin.mount | Create mount, mount details |
Pterodactyl\Models\DatabaseHost | admin.database_host | Create database host, database host details |
| Behaviour | Detail |
|---|---|
| Request shape | The create and update endpoints of each resource accept extensions: { [extensionId]: { [field]: value } }. |
| Values | A string, number, boolean, null, or a list of those. |
| Validation on create | The rules of every extension the administrator may change run, whether or not the request includes its values. |
| Validation on update | The rules of each extension the request includes run, even with no values. An extension left out keeps its stored values. |
| Errors | Reported as extensions.{id}.{field}. An extension whose methods throw during validation is recorded as failing, and the request gets a 422 under extensions.{id}. |
| Saving | A save that throws rolls back the whole change. |
| Responses | Responses about one resource include attributes.extensions. Lists leave it out. An extension whose values throws is recorded as failing and left out. |
| Authorization | A request that sends values authorize refuses gets a 403. Refused values are left out of responses. |
| Application API | Classes marked #[ApplicationApi] are also accepted and returned by the Application API's user, node, location, and server endpoints. |
| Availability | Values sent for a disabled or failed extension are ignored, and its stored values are kept. |
GET /api/admin/extensions/forms lists, for each admin form, the enabled extensions that add fields to it and that the signed-in administrator may change.
Events
The Panel dispatches these events from the Pterodactyl\Events\Extensions namespace. Each has a public $manifest property unless noted.
| Event | Dispatched |
|---|---|
ExtensionInstalling | Before an extension is installed. Also has $source. |
ExtensionInstalled | After an extension is installed. |
ExtensionEnabling | Before an extension is enabled. |
ExtensionEnabled | After an extension is enabled and its migrations have run. |
ExtensionDisabling | Before an extension is disabled. |
ExtensionDisabled | After an extension is disabled. |
ExtensionRemoving | Before an extension is removed. |
ExtensionRemoved | After an extension is removed. Has only $identifier. |
ExtensionLoadFailed | When an extension's provider throws while loading. Has $identifier, $phase, $reason, and $exception. |
Server Operation Events
listenToServerOperations receives Pterodactyl\Events\Server\OperationCompleted:
| Property | Value |
|---|---|
serverUuid | The server's UUID. |
operation | provision, install, reinstall, or backup. |
successful | Whether the operation succeeded. |
resourceUuid | The backup UUID for backups; the server UUID for other operations. |
These properties are read-only. Events dispatch after the database transaction commits. Provisioning is reported only after Wings accepts creation. Listener failures are recorded against the extension.
Job Progress
Pterodactyl\Services\Extensions\ExtensionJobProgress supports:
| Method | Description |
|---|---|
begin($extension, $user, $server = null, $permission = null) | Create an ExtensionJobSnapshot with a UUID, running status, and zero percent. |
update($extension, $id, $percent, $message = '', $status = 'running') | Update a snapshot. Percent must be 0–100 and cannot decrease; messages may be up to 1,000 characters. Completing sets percent to 100. |
find($extension, $id) | Read a snapshot, or null if absent or expired. Performs no authorization. |
visible($extension, $id, $user, $server = null) | Read a snapshot after checking its subject and the user's current access. Unavailable snapshots return 404. |
Status is running, completed, or failed. Finished snapshots cannot be updated. Each update increases sequence. The frontend payload contains id, extension, status, percent, message, sequence, and updated_at.
| GET endpoint | Subject |
|---|---|
/api/client/extension-progress/{extension}/{job} | A user-only job. |
/api/client/servers/{server}/extension-progress/{extension}/{job} | A job for that server. |
Both endpoints require authentication. User-only jobs and server jobs without a permission are private to their creator and root administrators. Server jobs with a permission require current server access and that permission, which must belong to the same extension. Disabled extensions and missing, expired, or inaccessible jobs return 404. Browsers do not cache responses.
Workers and web requests must share a cache store. extensions.progress_retention_seconds sets retention, defaulting to 3,600 seconds after the last update. Store durable results or audit records elsewhere.
Frontend SDK
Import runtime components and hooks from @pterodactyl/sdk. The Panel provides them through its import map, so they are not bundled into your extension. Build, testing, and generated-client helpers use the subpaths below.
Use definePterodactylExtension({ setup }) for an entry definition, or defineConfiguredExtension(parseConfig, { setup }) with generated configuration and screen types. setup is synchronous; its registrations take effect together once it succeeds.
Setup Context
setup receives an object with these properties:
| Property | Description |
|---|---|
meta | { id, version } of your extension. |
config | Your frontend setting values. |
slots.register(name, component) | Render a component in a slot. |
screens.register(id, importer, options?) | Attach a component to a screen declared in extension.json. The importer must return a module with a default export, such as () => import('./Screen'). options.visible is the predicate a when.runtime screen requires, and options.badge returns a live navigation badge. Both receive a ScreenContext with user, config, and the current server or resource. |
columns.register(name, column) | Add a typed column to a supported admin table. |
components.replace(name, replacement) | Register a presentation for a name declared in ui.components. Accepts a component or { load: () => import('./View') }. |
forms.extend(form, component) | Draw the extension's fields inside an admin form. See Form Fields for the form names. |
Component Replacements
The component catalog is exported as COMPONENT_NAMES. ReplacementProps<Name> contains a read-only model, the native Default component, and parts. Default accepts className and a partial parts map. Each part receives { model }, typed by ComponentPartProps<Name>.
| Name | Model | Native parts |
|---|---|---|
dashboard.serverCard | ServerCardModel | identity, address, metrics |
server.files.details | FileDetailsModel | icon, name, size, modified |
server.files.editor | FileEditorModel | notice, editor, language, actions |
server.files.manager | FileManagerModel | toolbar, list, selection |
ServerCardModel has identifier, uuid, name, nullable description, nullable address, and state. Its state is one of:
{ kind: 'loading' }.{ kind: 'unavailable', reason }, wherereasonissuspended,connection-error,maintenance,transferring,installing,restoring-backup, orunavailable.{ kind: 'ready', power, cpu, memory, disk }.powerisoffline,stopped,starting,running, orstopping. Each metric has numericvalue, numericlimit, and booleanalarm. CPU values are percentages; memory and disk values are bytes. A zero limit means unlimited.
FileDetailsModel has name, kind, size, and modifiedAt. kind is file, directory, archive, or symlink. Size is in bytes; modifiedAt is the API timestamp string.
server.files.editor replaces the editing surface of the file editor. FileEditorModel has path, name, isNew, content, language, readOnly, dirty, and saving, plus change(content), save(), and saveAs(name). For a new file, path is its directory and name is empty. language is a MIME type such as text/x-yaml. Call change after every edit; the Panel saves that text. save and saveAs resolve whether the file was saved.
server.files.manager replaces the file browser's toolbar, listing, and selection bar. FileManagerModel has directory, entries, truncated, loading, refreshing, selection, permissions, and actions. Each FileManagerEntry has name, path, kind, size, mimetype, mode, modeBits, modifiedAt, and openable, with at most 250 entries per directory. permissions has boolean create, update, delete, and archive. actions has open, navigate, newFile, select, refresh, createDirectory, rename, remove, copy, archive, extract, chmod, download, and upload, which run the Panel's own mutations. remove does not ask for confirmation. The file manager slots and the server.files.details replacement render only inside the native parts you keep.
The server card and file details are non-interactive content inside core links. The Panel keeps polling, permission-aware navigation, accessible link names, selection, menus, and modals. The native server-card identity part renders dashboard.serverRow.name.after; other server-row slots remain in the core frame.
Each declared name must have exactly one implementation in synchronous setup. Unknown names, undeclared registrations, duplicate registrations, or missing implementations fail the extension's atomic setup. Lazy importers return a module with a default React component. Use the importer form for lazy views instead of registering React.lazy.
A component has one enabled owner. Conflicting enable and update requests fail before activation. Enabling or disabling the extension toggles its replacements; reload open browser sessions to apply the change. Conflicting packages found at startup are skipped, with errors naming their claims.
Rows share one loading decision per page and component name. Loading has a five-second deadline; a timed-out implementation cannot replace the native view later on that page. Import and render failures restore the native view. Core state stays above the presentation boundary; extension-local state can be lost on failure. An uncontained suspension commits that mount to its native view; use local Suspense for extension-owned asynchronous content. Event and asynchronous callback failures need useExtensionAction, separate from render boundaries.
See Component Replacements for registration and testing examples.
Hooks
| Hook | Returns |
|---|---|
useCurrentUser() | The signed-in user. |
useSiteSettings() | The Panel's name, locale, and reCAPTCHA settings. |
useCurrentServer(select?) | The current server on server pages, or undefined elsewhere. |
useCurrentServerRequired(message?) | The current server. Throws on pages without a server. |
useCurrentServerUuid() | The current server's UUID, or undefined. |
useCurrentServerPermissions() | The current user's permissions on the current server. ['*'] for the owner. |
useServerPermission(permission, matchAny?) | Whether the current user has the permission, or every permission in a list. Set matchAny to true to require any one. |
usePermissions(permissions) | One boolean per permission. |
useServerWebsocketEvent(event, callback) | Calls callback for typed live server events, such as SocketEvent.STATUS, SocketEvent.CONSOLE_OUTPUT, or backup completed:{uuid}. Server pages only; callback failures are attributed to the extension. |
useCurrentResource() | { kind, resource } on an admin resource detail page, or null elsewhere. Check kind before reading resource-specific fields. |
useExtensionAction(name, callback) | A wrapped handler whose failures are reported against the extension. Use it for asynchronous actions in screens and slots. |
useExtensionTranslation(group = 'messages') | { locale, ready, t } for a registered resources/lang/{locale}/{group}.php group. Call inside an extension screen or slot. |
useExtensionJobProgress(job, { serverUuid }?) | A query result for the extension's job. Pass a server UUID for server jobs. Polls running work every 1.5 seconds, refetches after reconnecting, and stops on completion, failure, or an access error. |
Server Data
These factories return query options that use the Panel's transport and cache. Pass the server UUID for files, startup, and backups to share the native page's cache entries.
| Factory | Data |
|---|---|
serverQueryOptions(id) | A server lookup. |
serverFilesQueryOptions(uuid, directory) | The file listing for a directory. |
serverFileContentQueryOptions(uuid, file) | A file's contents as a string. |
serverStartupQueryOptions(uuid) | Startup variables and image configuration. |
serverBackupsQueryOptions(uuid, page = 1) | A page of backups. |
| Hook | Data |
|---|---|
useServerFiles(uuid, directory, select?) | File listing query result; optional typed selector. |
useServerFileContent(uuid, file) | File content query result. |
useServerStartup(uuid, select?) | Startup query result; optional typed selector. |
useServerBackups(uuid, page = 1, select?) | Backup query result; optional typed selector. |
invalidateServerData(uuid, domains) invalidates the native cache and returns a promise. Domains are server, files, fileContent, startup, and backups. File and backup invalidation covers all cached directories or pages for that server. Await it when your own mutation changes native data.
Mutations
Each hook returns isPending, error, data, mutateAsync(input), and reset(). Await mutateAsync from a guarded action handler. The hooks apply native cache updates and invalidation; startup writes are serialized per server.
| Hook | Input |
|---|---|
useCreateServerDirectory(uuid) | { directory, name } |
useRenameServerFiles(uuid) | { directory, files: [{ from, to }] } |
useDeleteServerFiles(uuid) | { directory, files: string[] } |
useWriteServerFile(uuid) | { file, content } |
useUpdateServerStartupVariable(uuid) | { key, value }; returns the updated variable. |
useSetServerDockerImage(uuid) | { image } |
useCreateServerBackup(uuid) | { name?, ignored?, isLocked }; returns the created backup. |
The hooks do not replace backend authorization. Check the user's permission before showing a write control.
Navigation
| Export | Description |
|---|---|
PanelLink | A link with a destination and optional exact. Uses the Panel router. |
usePanelNavigate() | A function taking a destination and returning a navigation promise. |
usePanelLocation() | The current RouteSlotData. |
resolvePanelDestination(destination) | Resolve a destination to its pathname, search, hash, and replace option. |
A core destination is { to, params?, search?, hash?, replace? }. to must be a supported core route; TypeScript checks required route parameters. An extension destination is { extension, screen, params?, search?, hash?, replace? }; pass the declared screen ID and the parameters needed for its screen and resource parent.
Table Columns
columns.register(name, { id, label, component }) supports these tables:
| Name | Component data |
|---|---|
admin.nodes | The native node resource. |
admin.servers | The native server resource. |
admin.eggs | The native egg resource. |
The component receives { data }, typed as ExtensionTableRows[Name]. Column IDs use lowercase letters, numbers, and hyphens, starting with a letter, up to 48 characters. Columns follow extension order and render after core columns. Each cell has an error boundary.
Components
| Component | Description |
|---|---|
Button | A button. Also Button.Text and Button.Danger. Supports isLoading, size, color, and isSecondary. |
Input, TextArea | Text fields. |
Select | A select menu, with single or multiple values. |
Checkbox, Switch | Checkboxes and switches. |
Label | A form label. |
Form, useAppForm | Forms built on TanStack Form, with ready-made field components. |
Dialog | Dialogs, including Dialog.Confirm. |
Alert | A notice, with type success, info, warning, or danger. |
ContentBox, TitledGreyBox | Content boxes. |
PageContentBlock, ServerContentBlock | Page wrappers that set the page title. Use ServerContentBlock on server screens. |
Spinner | A loading indicator. |
Icon | A Lucide icon. |
Can | Renders its children only if the user has a permission. |
CopyOnClick | Copies text when clicked. |
ScreenBlock, ServerError, NotFound | Full-page messages. |
Table | A native table with rows, columns, keyOf, and emptyState. Each column has id, label, and a cell(row) function. |
Tooltip | A themed tooltip with content, an element child, and optional placement. |
DropdownMenu, DropdownMenuItem, ContextDropdownMenu | Dropdown and context menus. |
Pagination | A native page selector. |
PanelLink | A link using a typed core or extension destination. |
The SDK also exports toast for notifications.
HTTP
| Export | Description |
|---|---|
http | The Panel's Axios instance, already signed in. |
httpErrorToHuman(error) | Turns an API error into a readable message. |
queryClient | The Panel's TanStack Query client. |
client from @pterodactyl/sdk/api | The shared transport used by generated extension clients. |
extensionProgressQueryOptions(extension, job, serverUuid?) | Query options keyed by extension, job, and subject. The progress endpoint enforces authorization. |
Types
| Type | Description |
|---|---|
SdkServer | A server from the Client API, with fields under attributes. |
SdkUser | The signed-in user. |
RouteSlotData | { pathname, search, params } for the current page. |
SubuserPermissionsSlotData | The data passed to the subuser permission editor slot. |
ExtensionConfig | The type of config. |
ExtensionConfigValue | A JSON value in frontend configuration. |
ScreenComponentProps | Route data including named parameters, with an optional typed resource for detail screens. |
ExtensionResourceContext | The typed admin resource union with kind and resource. |
FileManagerSlotData, FileRowSlotData | Native file context, selection, and callbacks; the row contract also has file. |
StartupFormSlotData | Startup configuration and native callbacks. |
AdminUserFormSlotData, AdminUserFormValues | The native user resource, bound form, and its existing field names. |
FormExtension, FormExtensionProps | A component registered with forms.extend, typed by its values and form, and its props: form, mode, resource, values, and field. |
ExtensionField | One field bound by field(name): id, name, value, setValue, and error. |
ExtensionFormName, ExtensionFormResources | The admin form names, and the resource type each one edits. |
ExtensionFieldValues, ExtensionFieldValue | One extension's values, keyed by field, and the type of one value. |
ExtensionTableRows, ExtensionTableName, ExtensionTableColumn | Native table row and column contracts. |
ComponentName, ComponentModels, ReplacementProps, ComponentPartProps, ComponentParts, DefaultComponentProps | Component replacement names, models, props, native parts, and default-view props. |
ServerCardModel, ServerCardState, ServerCardMetric, FileDetailsModel, FileEditorModel, FileManagerModel, FileManagerEntry, FileManagerPermissions, FileManagerActions | Read-only presentation models. |
ScreenCondition, ScreenMatcher, ScreenOptions, ScreenContext, ScreenBadgeValue | Screen when conditions and the visible and badge options. |
ReplacementImporter, ComponentReplacement | Direct component and lazy importer registration types. |
SdkFile, SdkBackup, SdkServerFiles, SdkServerStartup, SdkServerBackups | The native server resource response types. |
SdkMutation, SdkFileSelection | Mutation state and file selection input. |
PanelDestination, PanelLinkProps | Core and extension navigation contracts. |
ServerWebsocketEvent | Known server events and backup-specific completion event names. |
ExtensionJobProgress | The public job snapshot payload. |
Build Configuration
Build your extension with defineExtensionConfig from @pterodactyl/sdk/vite:
import { defineExtensionConfig } from '@pterodactyl/sdk/vite';
export default defineExtensionConfig({ entry: 'src/client/index.tsx' });Options: entry (required), outDir (default dist), and plugins.
Testing and Styling
| Package entry | Exports or use |
|---|---|
@pterodactyl/sdk/testing | createExtensionTestHost(options?), createTestServer(options?), createComponentTestHost(name, options), createTestServerCard(options?), createTestFileDetails(options?), createTestFileEditor(options?), createTestFileManager(options?), and createTestFileManagerEntry(options?). |
@pterodactyl/sdk/vitest | pterodactylTestAliases, pterodactylTestSetup, and pterodactylTestDirectory for Vitest configuration. |
@pterodactyl/sdk/openapi | externalizeExtensionApi(directory) connects generated code to the shared transport. The scaffold calls it after API generation. |
@pterodactyl/sdk/theme.css | Tailwind theme mappings to the Panel's design tokens. Import with theme and utilities; omit preflight. |
@pterodactyl/sdk/manifest.schema.json | Editor completion for extension.json. |
Test host options are extensionId, prefix (your ui.prefix), path, user, siteSettings, server, and a typed admin resource. The returned host has Wrapper, queryClient, navigate(destination), emitWebsocket(event, data), setConnected(connected), and dispose(). Use one host per test environment at a time; unmount its wrapper before disposing it.
createTestServer accepts identifier, uuid, name, owner, permissions, and status. In a source checkout, build the test runtime with npm run sdk:testing from the Panel root.
createComponentTestHost accepts a supported component name and { model, extensionId?, prefix? }, and returns Wrapper, typed replacement props, and dispose(). Call dispose() after unmounting the wrapper. Its defaults and parts are the Panel's native implementations. The model fixture helpers accept partial model overrides.
Frontend scaffolds include tests, CI, PostCSS, a stylesheet, and OpenAPI generation. Their npm run api:generate expects an extension openapi.yaml. To generate it, load the extension's routes, then run composer docs:openapi and npm run extension:api:generate -- {id} from the Panel root.
Entry CSS loads before setup; imported screen styles load with their screen chunk. Scope custom selectors to the extension. See Components and Styling.
Slots
Slots are named places on Panel pages where extensions render components. Register a component with slots.register(name, component). The data column shows the component's data prop.
Slot Data
| Contract | Fields and use |
|---|---|
RouteSlotData | pathname, search, and params. |
SdkServer | The current server, with resource fields under attributes. |
SubuserPermissionsSlotData | mode, selectedPermissions, editablePermissions, disabled, and setPermissions. |
FileManagerSlotData | server, directory, files, selectedFiles, isFetching, setSelectedFiles(names), and refresh(). |
FileRowSlotData | The file manager fields, plus the current file. |
StartupFormSlotData | server, configuration, isPending, canChangeDockerImage, setDockerImage(image), and refresh(). |
ExtensionResourceContext | kind and the matching native resource. Detail action slots narrow this to their own resource kind. |
AdminUserFormSlotData | kind: 'admin.user', resource, and the native form. Use form.AppField for existing user fields. |
The native user form fields are email, username, nameFirst, nameLast, password, rootAdmin, and language. Contributed controls share validation, pending state, submission, and persistence with the Panel's form. To add fields of your own, use Form Fields and forms.extend instead.
Navigation Slots
| Slot | Location | Position | data |
|---|---|---|---|
nav.items.before | Top navigation bar | Before | None |
nav.items.after | Top navigation bar | After | None |
Authentication Slots
| Slot | Location | Position | data |
|---|---|---|---|
auth.login.before | Login page | Before | RouteSlotData |
auth.login.after | Login page | After | RouteSlotData |
auth.login.form.after | Login form, below the Login button | After | RouteSlotData |
auth.checkpoint.before | Two-factor checkpoint page | Before | RouteSlotData |
auth.checkpoint.after | Two-factor checkpoint page | After | RouteSlotData |
auth.password.before | Forgot password page | Before | RouteSlotData |
auth.password.after | Forgot password page | After | RouteSlotData |
auth.passwordReset.before | Password reset page | Before | RouteSlotData |
auth.passwordReset.after | Password reset page | After | RouteSlotData |
Dashboard Slots
| Slot | Location | Position | data |
|---|---|---|---|
dashboard.before | Dashboard | Before | None |
dashboard.after | Dashboard | After | None |
dashboard.serverRow.before | Each server on the dashboard | Before | SdkServer |
dashboard.serverRow.after | Each server on the dashboard | After | SdkServer |
dashboard.serverRow.name.after | Each server's name on the dashboard | After | SdkServer |
dashboard.serverRow.metrics.after | Each server's resource usage on the dashboard | After | SdkServer |
Account Slots
| Slot | Location | Position | data |
|---|---|---|---|
account.navigation.before | Account navigation | Before | None |
account.navigation.after | Account navigation | After | None |
account.overview.before | Account overview | Before | None |
account.overview.after | Account overview | After | None |
account.api.before | API keys page | Before | RouteSlotData |
account.api.after | API keys page | After | RouteSlotData |
account.ssh.before | SSH keys page | Before | RouteSlotData |
account.ssh.after | SSH keys page | After | RouteSlotData |
account.activity.before | Activity page | Before | RouteSlotData |
account.activity.after | Activity page | After | RouteSlotData |
Server Slots
| Slot | Location | Position | data |
|---|---|---|---|
server.navigation.before | Server navigation | Before | SdkServer |
server.navigation.after | Server navigation | After | SdkServer |
server.console.before | Console page | Before | SdkServer |
server.console.power.before | Console power buttons | Before | SdkServer |
server.console.power.after | Console power buttons | After | SdkServer |
server.console.after | Console page | After | SdkServer |
server.files.before | File manager | Before | None |
server.files.after | File manager | After | None |
server.files.toolbar | File manager toolbar | Inside | FileManagerSlotData |
server.files.rowActions | File row and context menu | Actions | FileRowSlotData |
server.files.selectionActions | Selected-file action bar | Actions | FileManagerSlotData |
server.files.editor.before | File editor | Before | RouteSlotData |
server.files.editor.after | File editor | After | RouteSlotData |
server.databases.before | Databases page | Before | RouteSlotData |
server.databases.after | Databases page | After | RouteSlotData |
server.schedules.before | Schedules page | Before | RouteSlotData |
server.schedules.after | Schedules page | After | RouteSlotData |
server.schedules.detail.before | Schedule details page | Before | RouteSlotData |
server.schedules.detail.after | Schedule details page | After | RouteSlotData |
server.users.before | Users page | Before | RouteSlotData |
server.users.after | Users page | After | RouteSlotData |
server.users.create.before | Create subuser page | Before | RouteSlotData |
server.users.create.after | Create subuser page | After | RouteSlotData |
server.users.permissions.before | Subuser permission editor | Before | SubuserPermissionsSlotData |
server.backups.before | Backups page | Before | RouteSlotData |
server.backups.after | Backups page | After | RouteSlotData |
server.network.before | Network page | Before | RouteSlotData |
server.network.after | Network page | After | RouteSlotData |
server.startup.before | Startup page | Before | RouteSlotData |
server.startup.after | Startup page | After | RouteSlotData |
server.startup.form | Startup configuration | Inside | StartupFormSlotData |
server.settings.before | Settings page | Before | RouteSlotData |
server.settings.after | Settings page | After | RouteSlotData |
server.activity.before | Activity page | Before | RouteSlotData |
server.activity.after | Activity page | After | RouteSlotData |
Admin Slots
| Slot | Location | Position | data |
|---|---|---|---|
panel.navigation.before | Admin navigation | Before | None |
panel.navigation.after | Admin navigation | After | None |
panel.overview.before | Admin overview | Before | None |
panel.overview.after | Admin overview | After | None |
panel.users.before | Users list | Before | RouteSlotData |
panel.users.after | Users list | After | RouteSlotData |
panel.users.create.before | Create user page | Before | RouteSlotData |
panel.users.create.after | Create user page | After | RouteSlotData |
panel.users.detail.before | User details page | Before | RouteSlotData |
panel.users.detail.after | User details page | After | RouteSlotData |
panel.users.detail.actions | User detail header | Actions | admin.user resource context |
panel.users.detail.form | Native user form | Inside | AdminUserFormSlotData |
panel.locations.before | Locations list | Before | RouteSlotData |
panel.locations.after | Locations list | After | RouteSlotData |
panel.locations.detail.before | Location details page | Before | RouteSlotData |
panel.locations.detail.after | Location details page | After | RouteSlotData |
panel.nodes.before | Nodes list | Before | RouteSlotData |
panel.nodes.after | Nodes list | After | RouteSlotData |
panel.nodes.create.before | Create node page | Before | RouteSlotData |
panel.nodes.create.after | Create node page | After | RouteSlotData |
panel.nodes.detail.before | Node details page | Before | RouteSlotData |
panel.nodes.detail.after | Node details page | After | RouteSlotData |
panel.nodes.detail.actions | Node detail header | Actions | admin.node resource context |
panel.nodes.detail.about.before | Node About tab | Before | RouteSlotData |
panel.nodes.detail.about.after | Node About tab | After | RouteSlotData |
panel.nodes.detail.settings.before | Node Settings tab | Before | RouteSlotData |
panel.nodes.detail.settings.after | Node Settings tab | After | RouteSlotData |
panel.nodes.detail.configuration.before | Node Configuration tab | Before | RouteSlotData |
panel.nodes.detail.configuration.after | Node Configuration tab | After | RouteSlotData |
panel.nodes.detail.allocations.before | Node Allocations tab | Before | RouteSlotData |
panel.nodes.detail.allocations.after | Node Allocations tab | After | RouteSlotData |
panel.nodes.detail.servers.before | Node Servers tab | Before | RouteSlotData |
panel.nodes.detail.servers.after | Node Servers tab | After | RouteSlotData |
panel.servers.before | Servers list | Before | RouteSlotData |
panel.servers.after | Servers list | After | RouteSlotData |
panel.servers.create.before | Create server page | Before | RouteSlotData |
panel.servers.create.after | Create server page | After | RouteSlotData |
panel.servers.detail.before | Server details page | Before | RouteSlotData |
panel.servers.detail.after | Server details page | After | RouteSlotData |
panel.servers.detail.actions | Server detail header | Actions | admin.server resource context |
panel.servers.detail.about.before | Server About tab | Before | RouteSlotData |
panel.servers.detail.about.after | Server About tab | After | RouteSlotData |
panel.servers.detail.details.before | Server Details tab | Before | RouteSlotData |
panel.servers.detail.details.after | Server Details tab | After | RouteSlotData |
panel.servers.detail.build.before | Server Build tab | Before | RouteSlotData |
panel.servers.detail.build.after | Server Build tab | After | RouteSlotData |
panel.servers.detail.startup.before | Server Startup tab | Before | RouteSlotData |
panel.servers.detail.startup.after | Server Startup tab | After | RouteSlotData |
panel.servers.detail.databases.before | Server Databases tab | Before | RouteSlotData |
panel.servers.detail.databases.after | Server Databases tab | After | RouteSlotData |
panel.servers.detail.mounts.before | Server Mounts tab | Before | RouteSlotData |
panel.servers.detail.mounts.after | Server Mounts tab | After | RouteSlotData |
panel.servers.detail.manage.before | Server Manage tab | Before | RouteSlotData |
panel.servers.detail.manage.after | Server Manage tab | After | RouteSlotData |
panel.servers.detail.delete.before | Server Delete tab | Before | RouteSlotData |
panel.servers.detail.delete.after | Server Delete tab | After | RouteSlotData |
panel.databaseHosts.before | Database hosts list | Before | RouteSlotData |
panel.databaseHosts.after | Database hosts list | After | RouteSlotData |
panel.databaseHosts.create.before | Create database host page | Before | RouteSlotData |
panel.databaseHosts.create.after | Create database host page | After | RouteSlotData |
panel.databaseHosts.detail.before | Database host details page | Before | RouteSlotData |
panel.databaseHosts.detail.after | Database host details page | After | RouteSlotData |
panel.mounts.before | Mounts list | Before | RouteSlotData |
panel.mounts.after | Mounts list | After | RouteSlotData |
panel.mounts.create.before | Create mount page | Before | RouteSlotData |
panel.mounts.create.after | Create mount page | After | RouteSlotData |
panel.mounts.detail.before | Mount details page | Before | RouteSlotData |
panel.mounts.detail.after | Mount details page | After | RouteSlotData |
panel.eggs.before | Eggs list | Before | RouteSlotData |
panel.eggs.after | Eggs list | After | RouteSlotData |
panel.eggs.create.before | Create egg page | Before | RouteSlotData |
panel.eggs.create.after | Create egg page | After | RouteSlotData |
panel.eggs.detail.before | Egg details page | Before | RouteSlotData |
panel.eggs.detail.after | Egg details page | After | RouteSlotData |
panel.eggs.detail.actions | Egg detail header | Actions | admin.egg resource context |
panel.eggs.detail.configuration.before | Egg Configuration tab | Before | RouteSlotData |
panel.eggs.detail.configuration.after | Egg Configuration tab | After | RouteSlotData |
panel.eggs.detail.tags.before | Egg Tags tab | Before | RouteSlotData |
panel.eggs.detail.tags.after | Egg Tags tab | After | RouteSlotData |
panel.eggs.detail.variables.before | Egg Variables tab | Before | RouteSlotData |
panel.eggs.detail.variables.after | Egg Variables tab | After | RouteSlotData |
panel.eggs.detail.script.before | Egg Install script tab | Before | RouteSlotData |
panel.eggs.detail.script.after | Egg Install script tab | After | RouteSlotData |
panel.tags.before | Tags page | Before | RouteSlotData |
panel.tags.after | Tags page | After | RouteSlotData |
panel.activity.before | Admin activity page | Before | RouteSlotData |
panel.activity.after | Admin activity page | After | RouteSlotData |
panel.settings.before | Settings pages | Before | RouteSlotData |
panel.settings.after | Settings pages | After | RouteSlotData |
panel.extensions.before | Extensions page | Before | RouteSlotData |
panel.extensions.after | Extensions page | After | RouteSlotData |
panel.apiKeys.before | API keys page | Before | RouteSlotData |
panel.apiKeys.after | API keys page | After | RouteSlotData |
Last updated on