Skip to content

Notify types

A notify type is one kind of notification — “order placed”, “password reset”, “login code”. Its key ties together the subscriptions, templates and user settings stored in the database. The type registry holds the metadata of every type for an admin UI: name, group, tokens, defaults.

Terminal window
php artisan notify:make OrderOrdered
# app/Notifications/OrderOrderedNotify.php
php artisan notify:make Shop/OrderOrdered
# app/Notifications/Shop/OrderOrderedNotify.php

The Notify suffix is appended when missing. The generated class is queued (ShouldQueue + Queueable), takes $roleKey in the constructor and has a pre-filled typeDefinition():

final class OrderOrderedNotify extends BaseNotify implements ShouldQueue
{
use Queueable;
public function __construct(protected Order $order, protected string $roleKey) {}
public static function typeDefinition(): array
{
return [
'key' => 'OrderOrdered',
'name' => 'Order placed',
'group' => 'order',
'weight' => 20,
'desc' => 'Sent when the customer places an order',
'settings' => ['delay'],
'tokens' => [
['key' => '[order:number]', 'name' => 'Order number'],
['key' => '[user:name]', 'name' => 'Customer name'],
],
'defaults' => [
'mail' => ['subject' => 'Order placed', 'body' => 'Your order [order:number] has been received.'],
'messenger' => ['body' => 'New order [order:number]'],
],
];
}
}

All keys are listed in typeDefinition() keys.

public static function notifyKey(): string
{
return 'OrderOrdered';
}

On every boot the service provider scans the directories in config('notify-templates.discover') (default app_path('Notifications')) recursively and registers each class that extends BaseNotify and returns a non-empty key from typeDefinition(). Set discover to [] to turn it off, or scan another directory yourself:

NotifyTemplates::discoverIn(app_path('Domain/Notifications'));

How a file is recognized: the scanner reads the namespace line and the first line starting with class or final class, then checks the class with class_exists() and is_subclass_of(). Abstract base classes are skipped, which is what you want for your app’s own base notification.

For types that don’t map to a class, or are generated from data, register them in AppServiceProvider::boot():

use Fomvasss\NotifyTemplates\Facades\NotifyTemplates;
NotifyTemplates::registerTypes([
[
'key' => 'UserCreated',
'name' => 'User created',
'group' => 'user',
'weight' => 10,
'settings' => ['delay'],
'tokens' => [
['key' => '[user:name]', 'name' => 'Name'],
['key' => '[user:email]', 'name' => 'Email'],
],
],
]);
foreach (Order::statusesList() as $status) {
NotifyTemplates::registerType([
'key' => 'OrderStatus'.ucfirst($status['key']),
'name' => 'Status: '.$status['name'],
'group' => 'order',
]);
}

Or statically in the config:

'types' => [
['key' => 'UserCreated', 'name' => 'User created', 'group' => 'user'],
],

registerType() throws InvalidArgumentException when key is empty. Registering an existing key replaces it. Config types are registered after discovery.

NotifyTemplates::getTypes(); // ['OrderOrdered' => [...], ...]
NotifyTemplates::getTypes('order'); // only group "order"
NotifyTemplates::getType('OrderOrdered'); // the definition, or null
NotifyTemplates::getTypeChannels('OrderOrdered'); // its 'channels', or config('notify-templates.channels')
NotifyTemplates::isUserConfigurable('OrderOrdered');

getTypes() returns the types in registration order; sorting by weight within a group is up to your UI.

name, group, weight, desc, tokens, channels and settings are not used when sending — they exist for the admin screens you build. defaults is used: defaults.mail.subject and defaults.mail.body are the fallback when no template row matches (see Templates).

settings lists option keys that the admin can edit per role subscription; values are stored in notify_role_subscriptions.options. The package reads one of them itself, delay (minutes):

'settings' => ['delay'],
// notify_role_subscriptions.options = {"delay": 5}
NotifyTemplates::resolveDelay('OrderOrdered', 'client'); // 300 (seconds)

Any other key is yours — read it with $subscription->getOption('key'). A controller can save all declared keys without knowing them:

$settings = NotifyTemplates::getType($notifyKey)['settings'] ?? [];
if ($settings) {
$sub = NotifyRoleSubscription::firstOrNew(
['notify_key' => $notifyKey, 'role_key' => $roleKey, 'tenant_id' => null],
['is_active' => false, 'personal_only' => false, 'channels' => []],
);
$sub->options = array_merge($sub->options ?? [], $request->only($settings));
$sub->save();
}

registerType() stores the whole array as it is. Any key beyond the documented ones comes back from getType() untouched, so project-specific rules need no fork. The package ignores such keys; your admin UI enforces them:

public static function typeDefinition(): array
{
return [
'key' => 'UserOtp',
// ...
'user_configurable' => false,
// only this role may have a subscription/template: the code always goes to the user logging in
'allowed_roles' => ['client'],
// sent directly with $user->notify(), not through a role resolver — the admin UI locks the toggle
'always_sent' => true,
];
}
$allowed = NotifyTemplates::getType($key)['allowed_roles'] ?? null;
$roleAllowed = $allowed === null || in_array($role, $allowed, true);
abort_if(NotifyTemplates::getType($key)['always_sent'] ?? false, 422, 'This type is always sent');

Why always_sent makes sense: a type with 'user_configurable' => false sent directly falls back to default_channels even when its subscription is inactive, so disabling it in the UI would be a lie. A non-configurable type that still goes through a role resolver is disabled for real by an inactive subscription — the resolver returns no recipients for that role.

notify:make uses stubs/notify.stub from your project root when it exists:

Terminal window
mkdir -p stubs
cp vendor/fomvasss/laravel-notify-templates/src/Console/stubs/notify.stub stubs/notify.stub

The stub supports {{ namespace }}, {{ class }} and {{ notifyKey }}.