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:
- Create a PHP class implementing
PanelPluginInterface. - Implement the panel API calls and declare the capabilities your panel supports.
- 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:
| Key | Description |
|---|---|
key | Stored in config JSON, passed back via configure() |
label | Form label shown to the admin |
type | text, password, url, number, or select |
required | Boolean |
default | Default value |
description | Help text shown below the field |
options | For 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.
| Field | Required |
|---|---|
status | Required |
expiration_date | Required |
Products
syncProducts(): array Capability products
Fetches packages/products from your panel. Fields per item:
| Field | Required |
|---|---|
product_id | Required |
name | Required |
duration_months | Required |
connections | Optional |
credits | Optional |
panel_description | Optional |
Bouquets
syncBouquets(): array Capability bouquets
Fetches bouquets/channel groups from your panel.
| Field | Required |
|---|---|
bouquet_id | Required |
name | Required |
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.
| Field | Required |
|---|---|
panel_user_id | Required |
xtream_username | Required |
xtream_password | Required |
expiration_date | Required |
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.
| Capability | What it enables |
|---|---|
products | Sync packages and products from the panel into Panelr |
activations | Create, renew, and view line details |
terminations | Permanently cancel a line |
enable_disable | Temporarily enable or disable a line |
bouquets | Sync bouquet and channel groups |
bouquets_update | Update a line's assigned bouquets |
credits | Display reseller credit balance in the dashboard |
lines | Bulk import all lines from the panel |
trials | Enable trial line creation workflow |
connection_upgrade | Move 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 APIjson_encode,json_decode,urlencode,urldecodedate,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+evalchains, variable variables
One class per file. Your plugin file must contain exactly one PHP class.