Skip to content

Notifly in Joomla

In Joomla everything is extended through plugins. A small system plugin subscribes to events (onUserAfterSave, onContentAfterSave, onContactAfterSubmit) and sends a push to Notifly. No third-party libraries are needed — the built-in HTTP client of the Joomla framework is used.

We reuse the shared notifly_send() helper — the same one as for the other CMSes. Inside the plugin it is easiest to wrap it in a private method and keep the token and URL in the plugin parameters.

plugins/system/notifly/
├── notifly.xml
└── src/Extension/Notifly.php

notifly.xml — the manifest with url and token parameters:

<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>System - Notifly</name>
<author>Notifly</author>
<version>1.0.0</version>
<namespace path="src">Notifly\Plugin\System\Notifly</namespace>
<files>
<folder plugin="notifly">src</folder>
</files>
<config>
<fields name="params">
<fieldset name="basic">
<field name="url" type="url" label="Notifly URL"
default="https://your-notifly.example.com" />
<field name="token" type="text" label="App token"
default="AGdjfk_L.dKe8q" />
</fieldset>
</fields>
</config>
</extension>

src/Extension/Notifly.php — this is where our notifly_send() lives (as a private method) together with all the event subscriptions:

<?php
namespace Notifly\Plugin\System\Notifly\Extension;
\defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Uri\Uri;
final class Notifly extends CMSPlugin
{
/** Universal helper (same logic as in /cases/cms/). */
private function send(string $title, string $message, int $priority = 5): void
{
$url = $this->params->get('url');
$token = $this->params->get('token');
if (!$url || !$token) {
return;
}
try {
HttpFactory::getHttp()->post(
"$url/message?token=$token",
json_encode([
'title' => mb_substr($title, 0, 200),
'message' => mb_substr($message, 0, 1500),
'priority' => $priority,
], JSON_UNESCAPED_UNICODE),
['Content-Type' => 'application/json'],
5
);
} catch (\Throwable $e) {
// Never break the site request because of a notification.
}
}
/** New registration or user change. */
public function onUserAfterSave($user, $isNew, $success, $msg): void
{
if (!$success || !$isNew) {
return;
}
$this->send(
"👤 Новый пользователь: {$user['username']}",
"Email: {$user['email']}\n" .
"ID: {$user['id']}\n" .
"Профиль: " . Uri::root() . "administrator/index.php?option=com_users&task=user.edit&id={$user['id']}",
4
);
}
/** New or edited article / content item. */
public function onContentAfterSave($context, $article, $isNew): void
{
if ($context !== 'com_content.article') {
return;
}
$action = $isNew ? 'создан' : 'обновлён';
$this->send(
"📄 Материал $action: {$article->title}",
"ID: {$article->id}\n" .
"Категория: {$article->catid}\n" .
"Открыть: " . Uri::root() .
"administrator/index.php?option=com_content&task=article.edit&id={$article->id}",
$isNew ? 5 : 4
);
}
/** Form submission via com_contact. */
public function onContactAfterSubmit($contact, $data): void
{
$this->send(
"✉️ Обращение через контакт: {$contact->name}",
"Тема: " . ($data['contact_subject'] ?? '') . "\n" .
"Сообщение:\n" . mb_substr($data['contact_message'] ?? '', 0, 1000),
7
);
}
}

The system plugin is loaded very early, so it is also a convenient place to catch fatal errors via register_shutdown_function — for example, in onAfterInitialise:

public function onAfterInitialise(): void
{
register_shutdown_function(function () {
$err = error_get_last();
if ($err && \in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
$this->send(
"🔥 Fatal: {$err['message']}",
"Файл: {$err['file']}:{$err['line']}\n" .
"URL: " . ($_SERVER['REQUEST_URI'] ?? '-'),
10
);
}
});
}
  1. Zip the notifly folder and install it via System → Install → Extensions.
  2. Open System → Plugins, find “System - Notifly”, set the URL and token, and enable the plugin.

System plugins fire on every frontend and admin request — that is enough to catch all of the events listed above.

To keep the contact form or bot registrations from flooding the channel, cache the sending via the built-in \Joomla\CMS\Cache\Cache:

$cache = \Joomla\CMS\Factory::getCache('notifly', '');
$key = 'throttle_' . md5($title);
if ($cache->get($key)) {
return;
}
$cache->store(1, $key);
  • Instant reaction to enquiries. A com_contact enquiry — a push before the email even reaches the mailbox.
  • Content control. Every article edit is visible to the editor right away.
  • A cheap error monitor without Sentry for a small Joomla site.
  • Separate Notifly channels for content, users and errors.
  • Priorities by content category via plugin parameters.
  • Passing the article/user id into extras for a deep link.