Extensions

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:

KeyRequiredDescription
idYesThe 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.
nameYesThe display name.
versionYesThe version, up to 64 characters.
$schemaNoEditor schema, usually ./node_modules/@pterodactyl/sdk/manifest.schema.json in a frontend package.
requires.panelNoSupported Panel versions, using Composer constraints.
requires.sdkWith component replacementsSupported SDK versions, using Composer constraints.
requires.phpNoSupported PHP versions, using Composer constraints.
requires.extensionsNoRequired extension IDs mapped to version constraints. Each dependency must be enabled.
descriptionNoShort description shown in the admin area.
authorNoAuthor shown in the admin area.
iconNoIcon 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.
providerNoFully qualified class name of the service provider. Must extend Pterodactyl\Extensions\ExtensionProvider.
autoloadNoPSR-4 map of namespace prefixes to directories inside the extension, such as {"ServerNotes\\": "src"}.
routes.rootNoTop-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.entryWith uiThe frontend bundle. Must be dist/client.js.
ui.modeNoMust be native (the default).
ui.prefixWith Tailwind utilitiesThe 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.screensNoPages the extension adds, up to 64.
ui.componentsNoSupported component names this extension replaces. Names must be unique.

Each entry in ui.screens supports these keys:

KeyRequiredDescription
idYesThe screen's ID, used with screens.register. Lowercase letters, numbers, and hyphens, starting with a letter.
areaYesserver, account, or admin.
parentNoAn admin detail page: admin.node, admin.server, admin.user, or admin.egg. Only accepted with area: "admin".
pathYesA 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.labelNoNavigation tab label, up to 100 characters. Without nav, the page has no tab.
nav.exactNoHighlight the navigation item only on an exact path match.
nav.paramsFor parameterized navigationValues for every named parameter in the screen path.
nav.orderNoSort order among extension navigation items. Defaults to 0.
nav.groupNoGroup metadata shown as a tooltip, up to 100 characters.
nav.badgeNoShort label beside the navigation item, up to 32 characters.
nav.iconNoAny 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.
permissionNoOnly 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.eggTagsNoOnly 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.eggFeaturesNoLike when.eggTags, matched against the server's egg features.
when.matchNoall (the default) or any. Combines eggTags and eggFeatures, and requires both.
when.runtimeNotrue 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 parentURL 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

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.

Artisan Commands

CommandDescription
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:listList 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:

MethodDescription
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.

MethodURL prefixAccess
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 $prefixAnyone, 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:

MethodDescription
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:

MethodDescription
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:

GroupPermissions
Consolewebsocket.connect, control.console, control.start, control.stop, control.restart
Subusersuser.create, user.read, user.update, user.delete
Filesfile.create, file.read, file.read-content, file.update, file.delete, file.archive, file.sftp
Backupsbackup.create, backup.read, backup.delete, backup.download, backup.restore
Networkallocation.read, allocation.create, allocation.update, allocation.delete
Startupstartup.read, startup.update, startup.docker-image
Databasesdatabase.create, database.read, database.update, database.delete, database.view_password
Schedulesschedule.create, schedule.read, schedule.update, schedule.delete
Settingssettings.rename, settings.reinstall
Activityactivity.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.

MethodCalledReturns
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.

ModelAdmin formPages
Pterodactyl\Models\Useradmin.userCreate user, user details
Pterodactyl\Models\Serveradmin.serverCreate server, server Details tab
Pterodactyl\Models\Nodeadmin.nodeCreate node, node Settings tab
Pterodactyl\Models\Eggadmin.eggCreate egg, egg Configuration tab
Pterodactyl\Models\Locationadmin.locationNew location dialog, location details
Pterodactyl\Models\Mountadmin.mountCreate mount, mount details
Pterodactyl\Models\DatabaseHostadmin.database_hostCreate database host, database host details
BehaviourDetail
Request shapeThe create and update endpoints of each resource accept extensions: { [extensionId]: { [field]: value } }.
ValuesA string, number, boolean, null, or a list of those.
Validation on createThe rules of every extension the administrator may change run, whether or not the request includes its values.
Validation on updateThe rules of each extension the request includes run, even with no values. An extension left out keeps its stored values.
ErrorsReported as extensions.{id}.{field}. An extension whose methods throw during validation is recorded as failing, and the request gets a 422 under extensions.{id}.
SavingA save that throws rolls back the whole change.
ResponsesResponses about one resource include attributes.extensions. Lists leave it out. An extension whose values throws is recorded as failing and left out.
AuthorizationA request that sends values authorize refuses gets a 403. Refused values are left out of responses.
Application APIClasses marked #[ApplicationApi] are also accepted and returned by the Application API's user, node, location, and server endpoints.
AvailabilityValues 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.

EventDispatched
ExtensionInstallingBefore an extension is installed. Also has $source.
ExtensionInstalledAfter an extension is installed.
ExtensionEnablingBefore an extension is enabled.
ExtensionEnabledAfter an extension is enabled and its migrations have run.
ExtensionDisablingBefore an extension is disabled.
ExtensionDisabledAfter an extension is disabled.
ExtensionRemovingBefore an extension is removed.
ExtensionRemovedAfter an extension is removed. Has only $identifier.
ExtensionLoadFailedWhen an extension's provider throws while loading. Has $identifier, $phase, $reason, and $exception.

Server Operation Events

listenToServerOperations receives Pterodactyl\Events\Server\OperationCompleted:

PropertyValue
serverUuidThe server's UUID.
operationprovision, install, reinstall, or backup.
successfulWhether the operation succeeded.
resourceUuidThe 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:

MethodDescription
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 endpointSubject
/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:

PropertyDescription
meta{ id, version } of your extension.
configYour 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>.

NameModelNative parts
dashboard.serverCardServerCardModelidentity, address, metrics
server.files.detailsFileDetailsModelicon, name, size, modified
server.files.editorFileEditorModelnotice, editor, language, actions
server.files.managerFileManagerModeltoolbar, list, selection

ServerCardModel has identifier, uuid, name, nullable description, nullable address, and state. Its state is one of:

  • { kind: 'loading' }.
  • { kind: 'unavailable', reason }, where reason is suspended, connection-error, maintenance, transferring, installing, restoring-backup, or unavailable.
  • { kind: 'ready', power, cpu, memory, disk }. power is offline, stopped, starting, running, or stopping. Each metric has numeric value, numeric limit, and boolean alarm. 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

HookReturns
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.

FactoryData
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.
HookData
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.

HookInput
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.

ExportDescription
PanelLinkA 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:

NameComponent data
admin.nodesThe native node resource.
admin.serversThe native server resource.
admin.eggsThe 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

ComponentDescription
ButtonA button. Also Button.Text and Button.Danger. Supports isLoading, size, color, and isSecondary.
Input, TextAreaText fields.
SelectA select menu, with single or multiple values.
Checkbox, SwitchCheckboxes and switches.
LabelA form label.
Form, useAppFormForms built on TanStack Form, with ready-made field components.
DialogDialogs, including Dialog.Confirm.
AlertA notice, with type success, info, warning, or danger.
ContentBox, TitledGreyBoxContent boxes.
PageContentBlock, ServerContentBlockPage wrappers that set the page title. Use ServerContentBlock on server screens.
SpinnerA loading indicator.
IconA Lucide icon.
CanRenders its children only if the user has a permission.
CopyOnClickCopies text when clicked.
ScreenBlock, ServerError, NotFoundFull-page messages.
TableA native table with rows, columns, keyOf, and emptyState. Each column has id, label, and a cell(row) function.
TooltipA themed tooltip with content, an element child, and optional placement.
DropdownMenu, DropdownMenuItem, ContextDropdownMenuDropdown and context menus.
PaginationA native page selector.
PanelLinkA link using a typed core or extension destination.

The SDK also exports toast for notifications.

HTTP

ExportDescription
httpThe Panel's Axios instance, already signed in.
httpErrorToHuman(error)Turns an API error into a readable message.
queryClientThe Panel's TanStack Query client.
client from @pterodactyl/sdk/apiThe 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

TypeDescription
SdkServerA server from the Client API, with fields under attributes.
SdkUserThe signed-in user.
RouteSlotData{ pathname, search, params } for the current page.
SubuserPermissionsSlotDataThe data passed to the subuser permission editor slot.
ExtensionConfigThe type of config.
ExtensionConfigValueA JSON value in frontend configuration.
ScreenComponentPropsRoute data including named parameters, with an optional typed resource for detail screens.
ExtensionResourceContextThe typed admin resource union with kind and resource.
FileManagerSlotData, FileRowSlotDataNative file context, selection, and callbacks; the row contract also has file.
StartupFormSlotDataStartup configuration and native callbacks.
AdminUserFormSlotData, AdminUserFormValuesThe native user resource, bound form, and its existing field names.
FormExtension, FormExtensionPropsA component registered with forms.extend, typed by its values and form, and its props: form, mode, resource, values, and field.
ExtensionFieldOne field bound by field(name): id, name, value, setValue, and error.
ExtensionFormName, ExtensionFormResourcesThe admin form names, and the resource type each one edits.
ExtensionFieldValues, ExtensionFieldValueOne extension's values, keyed by field, and the type of one value.
ExtensionTableRows, ExtensionTableName, ExtensionTableColumnNative table row and column contracts.
ComponentName, ComponentModels, ReplacementProps, ComponentPartProps, ComponentParts, DefaultComponentPropsComponent replacement names, models, props, native parts, and default-view props.
ServerCardModel, ServerCardState, ServerCardMetric, FileDetailsModel, FileEditorModel, FileManagerModel, FileManagerEntry, FileManagerPermissions, FileManagerActionsRead-only presentation models.
ScreenCondition, ScreenMatcher, ScreenOptions, ScreenContext, ScreenBadgeValueScreen when conditions and the visible and badge options.
ReplacementImporter, ComponentReplacementDirect component and lazy importer registration types.
SdkFile, SdkBackup, SdkServerFiles, SdkServerStartup, SdkServerBackupsThe native server resource response types.
SdkMutation, SdkFileSelectionMutation state and file selection input.
PanelDestination, PanelLinkPropsCore and extension navigation contracts.
ServerWebsocketEventKnown server events and backup-specific completion event names.
ExtensionJobProgressThe 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 entryExports or use
@pterodactyl/sdk/testingcreateExtensionTestHost(options?), createTestServer(options?), createComponentTestHost(name, options), createTestServerCard(options?), createTestFileDetails(options?), createTestFileEditor(options?), createTestFileManager(options?), and createTestFileManagerEntry(options?).
@pterodactyl/sdk/vitestpterodactylTestAliases, pterodactylTestSetup, and pterodactylTestDirectory for Vitest configuration.
@pterodactyl/sdk/openapiexternalizeExtensionApi(directory) connects generated code to the shared transport. The scaffold calls it after API generation.
@pterodactyl/sdk/theme.cssTailwind theme mappings to the Panel's design tokens. Import with theme and utilities; omit preflight.
@pterodactyl/sdk/manifest.schema.jsonEditor 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

ContractFields and use
RouteSlotDatapathname, search, and params.
SdkServerThe current server, with resource fields under attributes.
SubuserPermissionsSlotDatamode, selectedPermissions, editablePermissions, disabled, and setPermissions.
FileManagerSlotDataserver, directory, files, selectedFiles, isFetching, setSelectedFiles(names), and refresh().
FileRowSlotDataThe file manager fields, plus the current file.
StartupFormSlotDataserver, configuration, isPending, canChangeDockerImage, setDockerImage(image), and refresh().
ExtensionResourceContextkind and the matching native resource. Detail action slots narrow this to their own resource kind.
AdminUserFormSlotDatakind: '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.

SlotLocationPositiondata
nav.items.beforeTop navigation barBeforeNone
nav.items.afterTop navigation barAfterNone

Authentication Slots

SlotLocationPositiondata
auth.login.beforeLogin pageBeforeRouteSlotData
auth.login.afterLogin pageAfterRouteSlotData
auth.login.form.afterLogin form, below the Login buttonAfterRouteSlotData
auth.checkpoint.beforeTwo-factor checkpoint pageBeforeRouteSlotData
auth.checkpoint.afterTwo-factor checkpoint pageAfterRouteSlotData
auth.password.beforeForgot password pageBeforeRouteSlotData
auth.password.afterForgot password pageAfterRouteSlotData
auth.passwordReset.beforePassword reset pageBeforeRouteSlotData
auth.passwordReset.afterPassword reset pageAfterRouteSlotData

Dashboard Slots

SlotLocationPositiondata
dashboard.beforeDashboardBeforeNone
dashboard.afterDashboardAfterNone
dashboard.serverRow.beforeEach server on the dashboardBeforeSdkServer
dashboard.serverRow.afterEach server on the dashboardAfterSdkServer
dashboard.serverRow.name.afterEach server's name on the dashboardAfterSdkServer
dashboard.serverRow.metrics.afterEach server's resource usage on the dashboardAfterSdkServer

Account Slots

SlotLocationPositiondata
account.navigation.beforeAccount navigationBeforeNone
account.navigation.afterAccount navigationAfterNone
account.overview.beforeAccount overviewBeforeNone
account.overview.afterAccount overviewAfterNone
account.api.beforeAPI keys pageBeforeRouteSlotData
account.api.afterAPI keys pageAfterRouteSlotData
account.ssh.beforeSSH keys pageBeforeRouteSlotData
account.ssh.afterSSH keys pageAfterRouteSlotData
account.activity.beforeActivity pageBeforeRouteSlotData
account.activity.afterActivity pageAfterRouteSlotData

Server Slots

SlotLocationPositiondata
server.navigation.beforeServer navigationBeforeSdkServer
server.navigation.afterServer navigationAfterSdkServer
server.console.beforeConsole pageBeforeSdkServer
server.console.power.beforeConsole power buttonsBeforeSdkServer
server.console.power.afterConsole power buttonsAfterSdkServer
server.console.afterConsole pageAfterSdkServer
server.files.beforeFile managerBeforeNone
server.files.afterFile managerAfterNone
server.files.toolbarFile manager toolbarInsideFileManagerSlotData
server.files.rowActionsFile row and context menuActionsFileRowSlotData
server.files.selectionActionsSelected-file action barActionsFileManagerSlotData
server.files.editor.beforeFile editorBeforeRouteSlotData
server.files.editor.afterFile editorAfterRouteSlotData
server.databases.beforeDatabases pageBeforeRouteSlotData
server.databases.afterDatabases pageAfterRouteSlotData
server.schedules.beforeSchedules pageBeforeRouteSlotData
server.schedules.afterSchedules pageAfterRouteSlotData
server.schedules.detail.beforeSchedule details pageBeforeRouteSlotData
server.schedules.detail.afterSchedule details pageAfterRouteSlotData
server.users.beforeUsers pageBeforeRouteSlotData
server.users.afterUsers pageAfterRouteSlotData
server.users.create.beforeCreate subuser pageBeforeRouteSlotData
server.users.create.afterCreate subuser pageAfterRouteSlotData
server.users.permissions.beforeSubuser permission editorBeforeSubuserPermissionsSlotData
server.backups.beforeBackups pageBeforeRouteSlotData
server.backups.afterBackups pageAfterRouteSlotData
server.network.beforeNetwork pageBeforeRouteSlotData
server.network.afterNetwork pageAfterRouteSlotData
server.startup.beforeStartup pageBeforeRouteSlotData
server.startup.afterStartup pageAfterRouteSlotData
server.startup.formStartup configurationInsideStartupFormSlotData
server.settings.beforeSettings pageBeforeRouteSlotData
server.settings.afterSettings pageAfterRouteSlotData
server.activity.beforeActivity pageBeforeRouteSlotData
server.activity.afterActivity pageAfterRouteSlotData

Admin Slots

SlotLocationPositiondata
panel.navigation.beforeAdmin navigationBeforeNone
panel.navigation.afterAdmin navigationAfterNone
panel.overview.beforeAdmin overviewBeforeNone
panel.overview.afterAdmin overviewAfterNone
panel.users.beforeUsers listBeforeRouteSlotData
panel.users.afterUsers listAfterRouteSlotData
panel.users.create.beforeCreate user pageBeforeRouteSlotData
panel.users.create.afterCreate user pageAfterRouteSlotData
panel.users.detail.beforeUser details pageBeforeRouteSlotData
panel.users.detail.afterUser details pageAfterRouteSlotData
panel.users.detail.actionsUser detail headerActionsadmin.user resource context
panel.users.detail.formNative user formInsideAdminUserFormSlotData
panel.locations.beforeLocations listBeforeRouteSlotData
panel.locations.afterLocations listAfterRouteSlotData
panel.locations.detail.beforeLocation details pageBeforeRouteSlotData
panel.locations.detail.afterLocation details pageAfterRouteSlotData
panel.nodes.beforeNodes listBeforeRouteSlotData
panel.nodes.afterNodes listAfterRouteSlotData
panel.nodes.create.beforeCreate node pageBeforeRouteSlotData
panel.nodes.create.afterCreate node pageAfterRouteSlotData
panel.nodes.detail.beforeNode details pageBeforeRouteSlotData
panel.nodes.detail.afterNode details pageAfterRouteSlotData
panel.nodes.detail.actionsNode detail headerActionsadmin.node resource context
panel.nodes.detail.about.beforeNode About tabBeforeRouteSlotData
panel.nodes.detail.about.afterNode About tabAfterRouteSlotData
panel.nodes.detail.settings.beforeNode Settings tabBeforeRouteSlotData
panel.nodes.detail.settings.afterNode Settings tabAfterRouteSlotData
panel.nodes.detail.configuration.beforeNode Configuration tabBeforeRouteSlotData
panel.nodes.detail.configuration.afterNode Configuration tabAfterRouteSlotData
panel.nodes.detail.allocations.beforeNode Allocations tabBeforeRouteSlotData
panel.nodes.detail.allocations.afterNode Allocations tabAfterRouteSlotData
panel.nodes.detail.servers.beforeNode Servers tabBeforeRouteSlotData
panel.nodes.detail.servers.afterNode Servers tabAfterRouteSlotData
panel.servers.beforeServers listBeforeRouteSlotData
panel.servers.afterServers listAfterRouteSlotData
panel.servers.create.beforeCreate server pageBeforeRouteSlotData
panel.servers.create.afterCreate server pageAfterRouteSlotData
panel.servers.detail.beforeServer details pageBeforeRouteSlotData
panel.servers.detail.afterServer details pageAfterRouteSlotData
panel.servers.detail.actionsServer detail headerActionsadmin.server resource context
panel.servers.detail.about.beforeServer About tabBeforeRouteSlotData
panel.servers.detail.about.afterServer About tabAfterRouteSlotData
panel.servers.detail.details.beforeServer Details tabBeforeRouteSlotData
panel.servers.detail.details.afterServer Details tabAfterRouteSlotData
panel.servers.detail.build.beforeServer Build tabBeforeRouteSlotData
panel.servers.detail.build.afterServer Build tabAfterRouteSlotData
panel.servers.detail.startup.beforeServer Startup tabBeforeRouteSlotData
panel.servers.detail.startup.afterServer Startup tabAfterRouteSlotData
panel.servers.detail.databases.beforeServer Databases tabBeforeRouteSlotData
panel.servers.detail.databases.afterServer Databases tabAfterRouteSlotData
panel.servers.detail.mounts.beforeServer Mounts tabBeforeRouteSlotData
panel.servers.detail.mounts.afterServer Mounts tabAfterRouteSlotData
panel.servers.detail.manage.beforeServer Manage tabBeforeRouteSlotData
panel.servers.detail.manage.afterServer Manage tabAfterRouteSlotData
panel.servers.detail.delete.beforeServer Delete tabBeforeRouteSlotData
panel.servers.detail.delete.afterServer Delete tabAfterRouteSlotData
panel.databaseHosts.beforeDatabase hosts listBeforeRouteSlotData
panel.databaseHosts.afterDatabase hosts listAfterRouteSlotData
panel.databaseHosts.create.beforeCreate database host pageBeforeRouteSlotData
panel.databaseHosts.create.afterCreate database host pageAfterRouteSlotData
panel.databaseHosts.detail.beforeDatabase host details pageBeforeRouteSlotData
panel.databaseHosts.detail.afterDatabase host details pageAfterRouteSlotData
panel.mounts.beforeMounts listBeforeRouteSlotData
panel.mounts.afterMounts listAfterRouteSlotData
panel.mounts.create.beforeCreate mount pageBeforeRouteSlotData
panel.mounts.create.afterCreate mount pageAfterRouteSlotData
panel.mounts.detail.beforeMount details pageBeforeRouteSlotData
panel.mounts.detail.afterMount details pageAfterRouteSlotData
panel.eggs.beforeEggs listBeforeRouteSlotData
panel.eggs.afterEggs listAfterRouteSlotData
panel.eggs.create.beforeCreate egg pageBeforeRouteSlotData
panel.eggs.create.afterCreate egg pageAfterRouteSlotData
panel.eggs.detail.beforeEgg details pageBeforeRouteSlotData
panel.eggs.detail.afterEgg details pageAfterRouteSlotData
panel.eggs.detail.actionsEgg detail headerActionsadmin.egg resource context
panel.eggs.detail.configuration.beforeEgg Configuration tabBeforeRouteSlotData
panel.eggs.detail.configuration.afterEgg Configuration tabAfterRouteSlotData
panel.eggs.detail.tags.beforeEgg Tags tabBeforeRouteSlotData
panel.eggs.detail.tags.afterEgg Tags tabAfterRouteSlotData
panel.eggs.detail.variables.beforeEgg Variables tabBeforeRouteSlotData
panel.eggs.detail.variables.afterEgg Variables tabAfterRouteSlotData
panel.eggs.detail.script.beforeEgg Install script tabBeforeRouteSlotData
panel.eggs.detail.script.afterEgg Install script tabAfterRouteSlotData
panel.tags.beforeTags pageBeforeRouteSlotData
panel.tags.afterTags pageAfterRouteSlotData
panel.activity.beforeAdmin activity pageBeforeRouteSlotData
panel.activity.afterAdmin activity pageAfterRouteSlotData
panel.settings.beforeSettings pagesBeforeRouteSlotData
panel.settings.afterSettings pagesAfterRouteSlotData
panel.extensions.beforeExtensions pageBeforeRouteSlotData
panel.extensions.afterExtensions pageAfterRouteSlotData
panel.apiKeys.beforeAPI keys pageBeforeRouteSlotData
panel.apiKeys.afterAPI keys pageAfterRouteSlotData
Edit on GitHub

Last updated on

On this page