Panelr

Build plugins for Panelr.

Panelr's open plugin system lets developers connect any IPTV panel with an API. Build once, distribute to any Panelr installation.

What is a plugin?

A Panelr plugin is a PHP class that connects a Panelr installation to an IPTV panel's API. Every IPTV provider uses a panel to manage their streams and subscribers: XtreamUI, custom panels, proprietary systems. Panelr is panel-agnostic by design. Instead of building native support for every panel that exists, Panelr uses plugins to bridge that gap.

When a provider installs your plugin, Panelr can create lines, renew subscriptions, sync products, update bouquets and manage subscribers automatically, all through your panel's API.

Open plugin system. Build a PHP plugin that connects your panel API to Panelr.

Looking to integrate Panelr into your own app or storefront? You don't need a plugin. Use the Panelr API instead. View API documentation.

How it works

Build your integration around the panel API and the plugin interface:

  1. Create a PHP class implementing PanelPluginInterface.
  2. Implement the panel API calls and declare the capabilities your panel supports.
  3. Test your plugin's configuration, connection, and supported actions against your panel.

Getting started

Plugin skeleton

Create a new PHP file named after your class, for example MyPanelPlugin.php. The filename must match the class name exactly.

<?php

class MyPanelPlugin implements PanelPluginInterface
{
    private array $config = [];

    public function getName(): string
    {
        return 'My Panel Plugin';
    }

    public function getVersion(): string
    {
        return '1.0.0';
    }

    public function getCapabilities(): array
    {
        return ['products', 'activations', 'bouquets'];
    }

    public function getRequiredConfigFields(): array
    {
        return [
            [
                'key'         => 'api_url',
                'label'       => 'Panel API URL',
                'type'        => 'url',
                'required'    => true,
                'default'     => '',
                'description' => 'Base URL of your panel API',
            ],
            [
                'key'         => 'api_key',
                'label'       => 'API Key',
                'type'        => 'password',
                'required'    => true,
                'default'     => '',
                'description' => 'Your panel API key',
            ],
        ];
    }

    public function configure(array $config): void
    {
        $this->config = $config;
    }

    public function testConnection(): array
    {
        $ch = curl_init($this->config['api_url'] . '/ping');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER     => ['X-Api-Key: ' . $this->config['api_key']],
            CURLOPT_TIMEOUT        => 10,
        ]);
        $response = curl_exec($ch);
        $code     = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return $code === 200
            ? ['success' => true,  'message' => 'Connected successfully.']
            : ['success' => false, 'message' => 'Could not connect to panel.'];
    }

    // Implement remaining interface methods...
}

Declare only what your panel supports. Panelr hides UI controls for capabilities you don't declare. If your panel doesn't support bouquet updates, don't declare bouquets_update, so your clients never see controls that don't work.

Plugin structure

Your plugin is a PHP class implementing PanelPluginInterface. Name the file to match the class, for example MyPanelPlugin.php.

Naming rules

  • Your PHP filename must match your class name exactly: MyPanelPlugin.php → class MyPanelPlugin
  • Class names must be unique across installed plugins
  • One class per file

Interface reference

Every plugin must implement all methods of PanelPluginInterface. Every method returns an array containing at minimum a success key. Methods gated behind a capability are only called when that capability is declared in getCapabilities(), but all methods must still be present in your class.

Identity

getName(): string Always required

Returns the display name of the plugin shown in the Panelr admin.

getVersion(): string Always required

Returns a semver version string, e.g. "1.0.0".

getCapabilities(): array Always required

Returns an array of capability strings your plugin supports. See Capabilities for the full list.

Configuration

getRequiredConfigFields(): array Always required

Returns field definitions Panelr uses to render a configuration form in the admin. Each field is an array with:

KeyDescription
keyStored in config JSON, passed back via configure()
labelForm label shown to the admin
typetext, password, url, number, or select
requiredBoolean
defaultDefault value
descriptionHelp text shown below the field
optionsFor select type only: ['value' => 'Label', ...]

configure(array $config): void Always required

Called by Panelr after loading your plugin, passing the saved configuration as a key-value array. Store it in a private property and use it in your API calls.

Connection

testConnection(): array Always required

Tests connectivity to the panel API. Called when an admin saves plugin settings.

['success' => true,  'message' => 'Connected successfully.']
['success' => false, 'message' => 'Could not connect to panel.']

Activations

createActivation(array $params): array Capability activations

Creates a new line on your panel. Panelr passes product_id, bouquet_ids, duration_months, and notes. Return the new line details:

['success' => true, 'data' => [
    'panel_user_id'   => '1234',
    'xtream_username' => 'abc123',
    'xtream_password' => 'xyz789',
    'expiration_date' => '2027-01-01 00:00:00',
    'xtream_host'     => 'http://panel.example.com:8080', // optional
    'm3u_url'         => 'http://...',                    // optional
]]

renewActivation(string $panelUserId, array $params): array Capability activations

Extends an existing line. Panelr passes expiration_date, product_id, duration_months, username, and password. Return the new expiration date:

['success' => true, 'data' => [
    'expiration_date' => '2028-01-01 00:00:00',
]]

cancelActivation(string $panelUserId, string $username, string $password): array Capability terminations

Permanently terminates a line. Returns ['success' => true] on success.

enableActivation(string $panelUserId, string $username, string $password): array Capability enable_disable

Re-enables a previously disabled line.

disableActivation(string $panelUserId, string $username, string $password): array Capability enable_disable

Temporarily disables a line without terminating it.

getActivationInfo(string $panelUserId, string $username, string $password): array Capability activations

Returns current status and details for a line. status is active, expired, disabled, or banned. Return any additional fields your panel provides.

FieldRequired
statusRequired
expiration_dateRequired

Products

syncProducts(): array Capability products

Fetches packages/products from your panel. Fields per item:

FieldRequired
product_idRequired
nameRequired
duration_monthsRequired
connectionsOptional
creditsOptional
panel_descriptionOptional

Bouquets

syncBouquets(): array Capability bouquets

Fetches bouquets/channel groups from your panel.

FieldRequired
bouquet_idRequired
nameRequired

syncLineBouquets(string $panelUserId, array $params): array Capability bouquets_update

Updates the bouquets assigned to an existing line. $params['bouquets'] contains the array of bouquet IDs to assign.

Other

syncLines(): array Capability lines

Fetches all lines from your panel for bulk import.

FieldRequired
panel_user_idRequired
xtream_usernameRequired
xtream_passwordRequired
expiration_dateRequired

getCreditBalance(): array Capability credits

Returns the reseller credit balance from your panel as a numeric value: ['success' => true, 'data' => 1500].

Adding connections to a running line (optional)

A panel that can move a running line to a package with more connections for the rest of its term declares the connection_upgrade capability and adds these two methods. They are not declared in PanelPluginInterface, so a plugin without them keeps loading unchanged. Panelr calls them only on a plugin that declares the capability. Panelr prices the upgrade for the customer and moves the line to the new plan once the panel confirms. The line's expiry must not change.

getUpgradeOptions(string $panelUserId, array $params): array Capability connection_upgrade

The packages the line can move to, each with what the panel charges the reseller today. $params carries the line's username and password. When there is nothing to offer, say why in unavailable_reason: line_expired, trial_line, unlimited_line, line_has_no_package or not_an_upgrade.

['success' => true, 'data' => [
    'targets' => [
        [
            'product_id'  => '14',        // your package ID, as syncProducts() reports it
            'connections' => 3,
            'charge'      => '3.000000',  // what the panel charges the reseller today, or null
        ],
    ],
    'unavailable_reason' => '',           // why targets is empty
]]

upgradeActivation(string $panelUserId, array $params): array Capability connection_upgrade

Moves the line to $params['product_id']. expected_charge is the charge the order was quoted: if the upgrade now costs more, refuse it, charge nothing, and answer with price_changed. order_id and line_index identify the order line, so a repeated call after a lost answer must not charge twice. A line already on the package is a finished upgrade: answer success.

// Success
['success' => true, 'data' => [
    'connections'     => 3,
    'charged'         => '3.000000',           // what the reseller was charged
    'expiration_date' => '2027-04-12 10:00:00', // unchanged
]]

// The charge rose since the order was quoted — nothing charged
['success' => false, 'price_changed' => true, 'message' => 'The upgrade now costs more than it was quoted.']

// Any other refusal — Panelr keeps the order open for the admin
['success' => false, 'message' => 'The reseller balance does not cover this upgrade.']

Capabilities

Declare only the capabilities your panel actually supports. Panelr uses them to show or hide admin controls. Undeclared capabilities are never displayed.

CapabilityWhat it enables
productsSync packages and products from the panel into Panelr
activationsCreate, renew, and view line details
terminationsPermanently cancel a line
enable_disableTemporarily enable or disable a line
bouquetsSync bouquet and channel groups
bouquets_updateUpdate a line's assigned bouquets
creditsDisplay reseller credit balance in the dashboard
linesBulk import all lines from the panel
trialsEnable trial line creation workflow
connection_upgradeMove a running line to a package with more connections for the rest of its term, for a prorated fee. Optional: see Adding connections.

Security rules

Follow these rules when writing your plugin.

Allowed

  • curl_* functions for HTTP requests to your panel API
  • json_encode, json_decode, urlencode, urldecode
  • date, time, strtotime, date_create
  • All standard string, array, and math functions

Blocked

  • System execution: exec, shell_exec, system, passthru, proc_open
  • Code evaluation: eval, assert, create_function
  • File system access: fopen, fwrite, file_put_contents, file_get_contents
  • Database access: mysqli_*, new PDO
  • File includes: include, require
  • Superglobals: $_GET, $_POST, $_SERVER, $_SESSION
  • Output: echo, print, var_dump
  • Obfuscation: base64_decode+eval chains, variable variables

One class per file. Your plugin file must contain exactly one PHP class.