---
title: "Writing checks — Alerts for Laravel | Roundly"
description: "Extend Check, implement check() and notification(), read the scheduled row’s meta, and return ok, warning, failed or skipped results."
url: https://roundly-consulting.com/open-source/docs/alerts-for-laravel/writing-checks
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Alerts for Laravel](https://roundly-consulting.com/open-source/docs/alerts-for-laravel.md)

# Writing checks

Extend RoundlyConsulting\\Alerts\\Check and implement check() and notification():

```php
use Illuminate\Notifications\Notification;
use RoundlyConsulting\Alerts\Check;
use RoundlyConsulting\Alerts\CheckResult;

class DiskUsageCheck extends Check
{
    public function check(): CheckResult
    {
        // $this->healthCheck holds the scheduled HealthCheck record and its meta.
        $serverId = $this->healthCheck?->meta['server_id'] ?? null;

        return CheckResult::ok(
            message: 'Disk usage within limits',
            meta: ['server_id' => $serverId],
        );

        // Return CheckResult::failed('Disk almost full', [...]) to trigger an alert.
    }

    public function notification(object $notifiable): Notification
    {
        return new DiskUsageNotification();
    }
}
```

$this->healthCheck is the scheduled HealthCheck row the check runs for, with its meta. It is null when the check runs without a row — for example alerts:check without --notifiable — so read it null-safely.

## A fuller example

Override name(), key(), description() or tags() to change how the check presents itself. Tags declared here merge with each row’s own tags. Reuse the bundled notification and pass $this->channels() to honour notifyVia():

```php
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Http;
use RoundlyConsulting\Alerts\Check;
use RoundlyConsulting\Alerts\CheckResult;
use RoundlyConsulting\Alerts\Notifications\HealthCheckFailedNotification;

class PaymentGatewayCheck extends Check
{
    public function name(): string
    {
        return 'Payment gateway';
    }

    public function tags(): array
    {
        return ['critical', 'billing'];
    }

    public function check(): CheckResult
    {
        $url = $this->healthCheck?->meta['url'] ?? 'https://payments.example.com/status';

        // No try/catch needed — a thrown exception becomes a failed result.
        $response = Http::timeout(5)->get($url);

        return $response->successful()
            ? CheckResult::ok('Gateway reachable', ['url' => $url])
            : CheckResult::failed('Gateway returned '.$response->status(), ['url' => $url]);
    }

    public function notification(object $notifiable): Notification
    {
        // Reuse the bundled notification and honour notifyVia() routing.
        return new HealthCheckFailedNotification($this, channels: $this->channels());
    }
}
```

Because each row carries its own meta, one check class can watch several services for the same owner. Each row has its own counters, alerts and notification throttle:

```php
// One check class, two monitors — each row carries its own target in meta.
Health::for($team)->monitor(PaymentGatewayCheck::class)
    ->everyMinute()
    ->meta(['url' => 'https://payments.example.com/status'])
    ->save();

Health::for($team)->monitor(PaymentGatewayCheck::class)
    ->everyMinute()
    ->meta(['url' => 'https://payouts.example.com/status'])
    ->save();
```

As written, running this code again adds two more rows. Override uniqueBy() — returning \['url'\] here — and scheduling the same URL again updates its row instead of stacking another (see Scheduling identity & reconfigure).

Checks are instantiated without constructor arguments when you register or schedule them by class name. Keep configuration in fluent setters on a registered instance, or in the row’s meta.

## Results and severity

A CheckResult carries a Status — ok, warning, failed or skipped — plus a message and a meta array:

```php
use RoundlyConsulting\Alerts\CheckResult;

CheckResult::ok();
CheckResult::warning('Disk filling up');
CheckResult::failed('Disk full');
CheckResult::skipped('Maintenance window');
```

| Status | Severity | Effect of a run |
| --- | --- | --- |
| `ok` | 0 | Resets the failure counter and counts towards recovery of an open alert. |
| `skipped` | 1 | Records the run and nothing else — no counters, no alert, no notification. |
| `warning` | 2 | Counts as a failure: opens an alert and notifies, just like failed. |
| `failed` | 3 | Counts as a failure: opens an alert and notifies. |

```php
use RoundlyConsulting\Alerts\CheckResult;
use RoundlyConsulting\Alerts\Enums\Status;

$result = CheckResult::failed('Disk full', ['free_bytes' => 1024]);

$result->status;   // Status::Failed
$result->message;  // 'Disk full'
$result->meta;     // ['free_bytes' => 1024]
$result->isOk;     // false — legacy flag, true only for Status::Ok

new CheckResult(true);                     // same as CheckResult::ok()
new CheckResult(false, 'Down');            // same as CheckResult::failed('Down')
new CheckResult(Status::Warning, 'Slow');  // any Status works too
```

You never need a try/catch in check(): a thrown exception becomes a failed result automatically, and its details go to your logs rather than into stored alerts (see Timeouts & error handling).

## The Status enum

Status keeps its domain helpers — isAlertable() and severity() — and adopts the Helpers trait from enums-for-laravel, so it is ready for select inputs, validation rules and API payloads:

```php
use RoundlyConsulting\Alerts\Enums\Status;

Status::Warning->isAlertable();  // true — opens an alert and notifies
Status::Failed->severity();      // 3 (ok 0, skipped 1, warning 2, failed 3)

Status::values();                // Collection: ok, warning, failed, skipped
Status::toOptions();             // Collection: 'ok' => 'Ok', 'warning' => 'Warning', …
Status::validationRule();        // 'in:ok,warning,failed,skipped'
Status::Failed->label();         // 'Failed'
Status::tryFromLabel('Warning'); // Status::Warning
```

Also available: names(), labels(), options(), readable(), fromName()/tryFromName(), fromLabel(), hasValue(), is()/isIn() and the when\* guards.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/alerts-for-laravel.md)
