Plugins
On this page
A plugin adds a feature to the site: an app, a contact form, a page of reviews, a second way of reading videos. The platforms the site downloads from (YouTube, TikTok ...) are not plugins but modules; they are explained in Platforms and modules.
Admin → Plugins
Each plugin has a card with its name, version, description and author, and the badges Enabled, Premium or Does not load.
- Enable runs the plugin's own set-up (its tables and defaults) and starts it. A plugin that needs a newer version of the script says so, for example "needs AIO Downloader 1.5.0 or newer".
- Disable stops it. Its settings and its data stay, so enabling it again brings everything back.
- Settings (for plugins that have some) opens a form inside the card; Save settings stores it.
- Open (for plugins with screens of their own) leads to those screens. They are also listed in the admin menu, in a group called Plugins above Settings; a red number there counts new items, such as reviews that wait for you.
- The bin button deletes a plugin after you confirm: it is switched off, its folder and its saved settings are removed. Tables it made stay in the database.
- Upload a plugin → Install plugin takes a ZIP with one folder that holds
plugin.jsonandplugin.php. A newer ZIP of an installed plugin replaces its files and keeps its settings. A module or a theme uploaded here is recognised and put where it belongs.
Free and premium
A plugin is free unless its plugin.json says "tier": "premium". Free plugins work with every licence. A premium plugin shows Premium and, until your licence includes it, the line "Your licence does not include this premium plugin yet", with Enable greyed out. Add it to your licence on license.faruk.bd, press Check now on Admin → License, and it can be switched on (see The licence key). If the licence later loses it, the plugin stops running.
Uploading ZIP files can be switched off for a licence; the upload then says so.
When a plugin fails
- Does not load: the plugin's files have an error. The badge shows on its card, Enable cannot be pressed, and the reason is in the Application log on System check ("Extension failed to load").
- Fails while starting: the rest of the site keeps working and the log says "Plugin boot failed" with the reason. The plugin stays switched on, so press Disable until it is fixed or updated.
- You cannot open the admin panel any more: rename the folder of the plugin by FTP or the File Manager so that it begins with an underscore, for example
plugins/reviews→plugins/_reviews. The script skips such folders, so the plugin is off at once. Rename it back when it is fixed; its settings are still there.
The extensions on faruk.bd
All eleven are free on license.faruk.bd. Nine are plugins (Admin → Plugins); the two themes are switched on under Admin → Themes.
| Name | What it adds | Chapter |
|---|---|---|
| App Builder | An installable web app at once, plus Android and iPhone projects; the Android app receives links shared from other apps. | App Builder |
| yt-dlp Bridge | Runs yt-dlp: private YouTube videos with the visitor's cookies, and a second chance when a module gives up. | yt-dlp Bridge |
| External API Fallback | Asks a Cobalt-compatible API when a module gives up. | External API Fallback |
| Recent Downloads | A "Recently downloaded" strip on the home page and a download counter. | Recent Downloads |
| Nova Theme | A complete second design; the theme of a new installation. | Nova Theme |
| Midnight Theme | A darker, glassy variation of the classic layout. | Midnight Theme |
| Contact Form | A form for the Contact page with a spam wall without puzzles, an inbox, replies by e-mail. | Contact Form |
| What's New | A public "What's new" page, written in every language of the site. | What's New |
| Tools | Small helpers for visitors; the first one joins a video and an audio file in the browser. | Tools |
| Platform Requests | Visitors ask for a site to be supported and like each other's requests. | Platform Requests |
| Reviews | Stars and a few words, with approval, answers and a block on the home page. | Reviews |
Writing your own plugin
A plugin is a folder plugins/<slug>/; the folder name must equal the slug. It needs two files.
plugin.json describes it:
| Field | Meaning |
|---|---|
name, slug, version, description | what the Plugins page shows |
author, author_uri | "By ..." on the card |
icon | a Bootstrap icon, for example bi-phone |
requires | the oldest script version it runs on |
tier | free (the default) or premium |
plugin.php returns an object that extends AbstractPlugin. In boot(), which runs on every request while the plugin is on, it registers what it needs:
- hooks:
add_filter()forhtml_head,html_footer,html_after_header,html_home_after_hero,html_page_after,html_results_before_formats,page_content,media_result,media_fallback,menu_items,admin_nav,site_credit_html,front_layout_vars,mail_transportand more;add_action()fordownload_start,media_fetched,settings_savedandcron; - pages:
get('path', fn)andpost(...), orfront_route('path', fn)for a page at/<language>/pathin every language, drawn withrender_front_page($title, $html).
Other methods: install() runs when the plugin is enabled, uninstall() when it is disabled. settingsFields() lists the fields of its settings form; read them with $this->setting('name', $default). adminMenu() lists its admin screens (page, label, icon, count) and adminPage($page) draws them at /admin/plugin/<slug>/<page>. Texts go in languages/<code>.php (an array of key and text) and are printed with t('key').
A small example
This is the Recent Downloads plugin, shortened. Its plugin.json:
{
"name": "Recent Downloads",
"slug": "recent-downloads",
"version": "1.0.0",
"type": "feature",
"description": "Shows a \"Recently downloaded\" strip on the homepage.",
"author": "MD OMAR FARUK",
"author_uri": "https://faruk.bd"
}
Its plugin.php:
<?php
declare(strict_types=1);
return new class extends AbstractPlugin {
public function boot(): void
{
add_filter('html_home_after_hero', [$this, 'render'], 10);
}
public function settingsFields(): array
{
return [
['name' => 'count', 'label' => 'How many titles', 'type' => 'number', 'default' => '8'],
['name' => 'heading', 'label' => 'Heading', 'type' => 'text', 'default' => 'Recently downloaded'],
];
}
public function render(string $html): string
{
$n = max(1, min(30, (int) $this->setting('count', 8)));
$rows = cache_remember('plugin:recent-downloads', 120, fn(): array => db_rows(
'SELECT `title` FROM ' . table('history') . ' WHERE `title` <> "" ORDER BY `id` DESC LIMIT ' . $n));
if (!$rows) {
return $html;
}
$out = '<section class="section"><div class="container"><strong>'
. e((string) $this->setting('heading', 'Recently downloaded')) . '</strong> ';
foreach ($rows as $r) {
$out .= '<span class="badge">' . e(str_limit((string) $r['title'], 40)) . '</span> ';
}
return $html . $out . '</div></section>';
}
};
A filter receives the HTML so far and returns it with its own part added. Escape what you print with e(), keep boot() light, and never call a function of another plugin: it may be switched off.