Extend Linsei with your own code (without touching the core) using the hooks system. Full guide with a working example.
An add-on is a folder inside plugins/ with a plugin.php file. Linsei loads it automatically on startup. Your code «hooks» into specific points of the program (the hooks) to add features, without modifying the core. That way your add-on survives Linsei updates.
plugins/
└─ my-addon/
└─ plugin.phpEvery plugin.php starts with this safety line (it prevents direct execution):
<?php if(!defined("PL_APP")){http_response_code(403);exit;}
/* Add-on: My first add-on */
pl_add_action('dashboard_after', function ($isAdmin) {
echo '<div class="card">Hello from my add-on!</div>';
});Copy that folder into plugins/ and, on reloading the panel, you'll see your card. That's it: you have an add-on.
There are two ways to hook in:
pl_add_action): run your code at a given moment (e.g. render something in the panel).pl_add_filter): receive a value, transform it and return it (e.g. change the target URL).// Action: do something
pl_add_action('link_created', function ($slug, $url) {
// e.g. notify a webhook
});
// Filter: transform a value (always return it)
pl_add_filter('link_destination', function ($url) {
return $url; // here you could modify the URL
});| Hook | Type | When | Arguments |
|---|---|---|---|
init | action | on every request | - |
admin_menu | action | when the sidebar is drawn | $user |
dashboard_after | action | at the end of the home panel | $isAdmin |
links_toolbar | action | Links button bar | - |
link_form_fields | action | in the link form | $link |
link_row_actions | action | actions on each row | $link |
help_after | action | at the end of Help | - |
link_created | action | when a link is created | $slug, $url |
link_saved | action | on save (create/edit) | $id, $post |
link_clicked | action | on every click | $link |
link_destination | filter | transforms the target on shortening | $url |
link_redirect | filter | decides the redirect | $decision, $link, $ctx |
redirect_interstitial | filter | HTML before redirecting | $html, $link, $target, $ctx |
pl_add_plugin_menu(title, url, icon, route) you add a menu entry, and in init you handle that route. Icons come from Bootstrap Icons.This add-on colors each link in the Links list by its clicks: red for few visits, amber for so-so and green for many. Tweak $few and $many to taste:
<?php if(!defined("PL_APP")){http_response_code(403);exit;}
/* Add-on: Visit traffic light */
pl_add_action('link_row_actions', function ($link) {
$clicks = (int)($link['click_count'] ?? 0);
$few = 10; // fewer than 10 clicks: red
$many = 100; // more than 100 clicks: green
if ($clicks < $few) { $color = '#dc2626'; } // red
elseif ($clicks > $many) { $color = '#16a34a'; } // green
else { $color = '#f59e0b'; } // amber
echo '<span style="background:' . $color . ';color:#fff;'
. 'border-radius:999px;padding:1px 8px;font-size:.75rem">'
. $clicks . '</span>';
});To avoid hardcoding the numbers, read them from the settings store (the same one the core uses for SMTP) with pl_app_setting() and give the add-on its own page where the admin changes them:
<?php if(!defined("PL_APP")){http_response_code(403);exit;}
/* Add-on: configurable traffic light */
// 1) In the traffic light, read the thresholds from the store (with defaults)
$few = (int) pl_app_setting('semaforo_few', 10);
$many = (int) pl_app_setting('semaforo_many', 100);
// 2) On the add-on page, save whatever the admin submits
if (($_POST['save'] ?? '') !== '') {
pl_app_settings_save([
'semaforo_few' => (int)($_POST['few'] ?? 10),
'semaforo_many' => (int)($_POST['many'] ?? 100),
]);
}
// 3) Add its entry to the panel menu (admin only)
pl_add_plugin_menu('Traffic light', '/admin/traffic-light', 'bi-sliders', '/admin/traffic-light');data/settings.json, via pl_app_setting() and pl_app_settings_save() (the same store as the core SMTP). The admin changes the thresholds without touching the code.pl_my_) to avoid clashing with other add-ons.When it works, zip the folder of your add-on into a .zip and upload it from the panel:
.zip and it installs instantly.