---
title: "Database schema & key types — Alerts for Laravel | Roundly"
description: "The four tables, their columns, and how key_type types the owner morph columns for bigint, UUID or ULID models."
url: https://roundly-consulting.com/open-source/docs/alerts-for-laravel/database-schema
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)

# Database schema & key types

Four tables back the package:

| Table | Columns | Notes |
| --- | --- | --- |
| `health_checks` | id, notifiable\_type + notifiable\_id, health\_check, frequency (nullable), max\_attempts, decay\_minutes, consecutive\_failures, consecutive\_successes, tags (json), meta (json), identity\_hash (char(64), nullable, unique), timestamps, deleted\_at | One row per scheduled check per owner. A null frequency marks an on-demand row seeded by run() — the scheduler never queues it. identity\_hash is set only on a live schedule of a check that declares uniqueBy(). Soft deletes. |
| `alerts` | id, notifiable\_type + notifiable\_id, health\_check\_id, status, escalation\_level, message (text), meta (json), triggered\_at, recovered\_at, open\_slot, timestamps, deleted\_at | One row per incident — recovered\_at is null while it is open. open\_slot is 1 while open and null once recovered; a unique (health\_check\_id, open\_slot) index admits one open alert per check. Soft deletes; indexed on owner + health check. |
| `alert_silences` | id, key, notifiable\_type + notifiable\_id (nullable), reason, starts\_at, ends\_at, identity\_hash (char(64), nullable, unique), timestamps | Mute records: a check key, a tag or \*, optionally scoped to one owner. Null starts\_at means active immediately; null ends\_at means until unmuted. identity\_hash holds one silence per key and scope, so mute() updates instead of inserting. |
| `health_check_runs` | id, health\_check\_id, status, duration\_ms, message (text), meta (json), ran\_at | One immutable row per executed run, no timestamps. Indexed on health check + ran\_at. |

alerts and health\_check\_runs carry a foreign key onto health\_checks that cascades on delete — which is why health\_checks is published first. HealthCheck and Alert use soft deletes, so the cascade applies when a row is force-deleted.

## Owner key type

Owners are polymorphic (notifiable\_type + notifiable\_id), so any Eloquent model can own health checks. key\_type types the notifiable\_id column on health\_checks, alerts and alert\_silences:

| key\_type | Notifiable columns | Use when your owners… |
| --- | --- | --- |
| `bigint` | morphs() — unsignedBigInteger id | use Laravel’s default auto-incrementing keys (the default) |
| `uuid` | uuidMorphs() | use HasUuids |
| `ulid` | ulidMorphs() | use HasUlids |

```bash
# Set before you run the migrations — the notifiable columns are typed from it
ALERTS_KEY_TYPE=uuid
```

All your owner models must share one key type. The value is case-insensitive; an absent or blank key means bigint, and an unrecognized value throws InvalidConfigurationException when the migrations run instead of falling back. The package’s own tables always keep auto-incrementing ids.

## Where options live

Per-monitor options that have no column — failAfter, recoverAfter, timeout, channel routing and the escalation policy — are stored inside the row’s meta json under reserved keys (see Scheduling checks), next to any meta of your own.

## On-demand rows and open alerts

Running a check now for an owner that has no schedule for it seeds an on-demand row with a null frequency. It keeps that owner’s counters, history and alerts for the check between manual runs, but the scheduler never queues it (see Running checks). On alerts, the unique (health\_check\_id, open\_slot) index guarantees at most one open alert per check, even when two failing runs overlap. It relies on NULLs never colliding in a unique index, as on MySQL and PostgreSQL; SQL Server treats them as equal, so it is not supported — the identity\_hash indexes would also refuse a second row without an identity there. Run and alert messages are text columns, stored up to 1000 characters.

## Identity columns

health\_checks.identity\_hash and alert\_silences.identity\_hash (1.2) are nullable char(64) columns, each with a unique index. The first identifies a live schedule of a check that declares uniqueBy() — null for on-demand rows, other checks and soft-deleted rows (see Scheduling identity & reconfigure); the second identifies an immediate silence by its key and scope, so mute() updates instead of inserting. A silence your code writes straight through the model has no identity and is never merged.

## 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)
