This release is a pre-release and may not be stable for production use.
datasette-alerts
Alerts and notifications for Datasette. Supports row-based alerts (new rows in a table) and custom alert types defined by plugins.
How It Works
datasette-alerts uses datasette-cron for scheduling. When an alert is created, a cron task is registered that periodically checks for new data and sends notifications through configured destinations.
Built-in alert types:
- Cursor alerts — poll a table for rows newer than a timestamp cursor
- Trigger alerts — SQLite INSERT trigger queues new rows for processing
Custom alert types — plugins can register their own alert logic via the datasette_alerts_register_alert_types hook.
Notifier Plugins
| Plugin | Description |
|---|---|
datasette-alerts-slack |
Send Slack messages |
datasette-alerts-discord |
Send Discord messages |
datasette-alerts-ntfy |
Send ntfy.sh notifications |
datasette-alerts-desktop |
Local desktop notifications |
Writing a Notifier Plugin
Notifier plugins implement the datasette_alerts_register_notifiers hook and return subclasses of Notifier.
from datasette import hookimpl
from datasette_alerts import Notifier, Message
from wtforms import Form, StringField
@hookimpl
def datasette_alerts_register_notifiers(datasette):
return [MyNotifier()]
class MyNotifier(Notifier):
slug = "my-notifier"
name = "My Notifier"
description = "Send alerts somewhere"
async def get_config_form(self):
class ConfigForm(Form):
webhook_url = StringField("Webhook URL")
return ConfigForm
async def send(self, config: dict, message: Message):
url = config["webhook_url"]
# deliver message.text (and optionally message.subject)
Notifier API
Notifier (abstract base class)
| Property / Method | Description |
|---|---|
slug |
Unique identifier (required) |
name |
Display name (required) |
description |
Short description |
icon |
SVG string for the UI |
get_config_form() |
Return a WTForms Form class for destination config |
get_config_element() |
Return a ConfigElement for web component config UI |
send(config, message) |
Deliver a Message to the destination described by config |
Message
Message(text: str, *, subject: str | None = None)
Writing a Custom Alert Type
Custom alert types let plugins define their own checking logic. datasette-alerts handles the scheduling, notification delivery, and logging.
Minimal Example
from datasette import hookimpl
from datasette_alerts import AlertType, Message
class MyAlertType(AlertType):
slug = "my-check"
name = "My Custom Check"
description = "Checks something and alerts when it finds results"
async def check(self, datasette, alert_config, database_name, last_check_at):
db = datasette.get_database(database_name)
# Your custom logic here — query tables, check conditions, etc.
result = await db.execute("SELECT * FROM my_queue WHERE status = 'pending'")
messages = []
ids = []
for row in result.rows:
ids.append(row[0])
messages.append(Message(f"New item: {row[1]}"))
# Mark as processed
if ids:
placeholders = ",".join("?" for _ in ids)
await db.execute_write(
f"UPDATE my_queue SET status = 'done' WHERE id IN ({placeholders})",
ids,
)
return messages
@hookimpl
def datasette_alerts_register_alert_types(datasette):
return [MyAlertType()]
AlertType API
AlertType (abstract base class)
| Property / Method | Description |
|---|---|
slug |
Unique identifier (required) |
name |
Display name (required) |
description |
Short description |
icon |
SVG string for the UI |
check(datasette, alert_config, database_name, last_check_at) |
Run check logic, return list[Message] |
get_config_element() |
Optional web component for alert configuration UI |
get_config_form() |
Optional WTForms form for configuration |
check() Method
async def check(
self,
datasette, # Datasette instance
alert_config: dict, # from the alert's custom_config column
database_name: str, # which database this alert is scoped to
last_check_at: str | None, # ISO timestamp of last check, or None on first run
) -> list[Message]:
Return a list of Message objects to send. Empty list means nothing to report. The handler calls check() on the schedule defined by the alert's frequency, sends any returned messages through the alert's subscriptions, and updates last_check_at.
How Custom Alerts Get Scheduled
When a custom alert is created (via the API or a plugin's own routes):
- A row is inserted into
datasette_alerts_alertswithalert_type="custom:{slug}" - A cron task is registered with
datasette-cronat the alert's frequency - Each tick, the
custom_alert_handlerlooks up theAlertTypeby slug via the plugin hook - Calls
check()with the alert's config and database - Sends any returned messages to all subscriptions
- Updates
last_check_atand logs the check
Creating Custom Alerts Programmatically
from datasette_alerts.internal_db import InternalDB, NewAlertRouteParameters, NewSubscription
internal_db = InternalDB(datasette.get_internal_database())
params = NewAlertRouteParameters(
database_name="mydb",
alert_type="custom:my-check",
frequency="+1 second",
custom_config={"key": "value"},
subscriptions=[
NewSubscription(destination_id="dest-id", meta={"aggregate": True})
],
)
alert_id = await internal_db.new_alert(params)
# Register the cron task
from datasette_alerts import _register_cron_task_for_alert
from types import SimpleNamespace
await _register_cron_task_for_alert(datasette, SimpleNamespace(
id=alert_id,
alert_type="custom:my-check",
frequency="+1 second",
))
Public API
send_to_destination()
Send a message through a configured destination without creating an alert:
from datasette_alerts import send_to_destination, Message
await send_to_destination(datasette, destination_id, Message("Hello!"))
Raises DestinationNotFound or NotifierNotFound on errors.
trigger_alert_check()
Trigger an immediate check for an alert, outside its normal schedule:
from datasette_alerts import trigger_alert_check
await trigger_alert_check(datasette, alert_id)
API Endpoint: List Alert Types
GET /-/{database}/datasette-alerts/api/alert-types
Returns registered custom alert types with their slug, name, description, and config element info.
Data Models
Query results from InternalDB return typed dataclasses:
from datasette_alerts.models import AlertRecord, AlertForCheck, AlertDetail
AlertRecord
Returned by get_all_alerts(). Fields: id, database_name, table_name, id_columns, timestamp_column, frequency, alert_type, custom_config, last_check_at.
AlertForCheck
Returned by get_alert_for_check(). Same as AlertRecord plus cursor.
AlertDetail
Returned by get_alert_detail(). Full alert info including nested subscriptions: list[SubscriptionDetail] and logs: list[AlertLogEntry], plus custom_config, last_check_at, next_deadline, alert_created_at.
AlertCleanupInfo
Returned by delete_alert(). Fields: alert_type, database_name, table_name.
Config: WTForms vs Web Components
There are two ways to provide a destination (or alert type) configuration UI:
WTForms (simple)
Override get_config_form() and return a WTForms Form class.
ConfigElement (rich UI)
Return a ConfigElement that declares a custom HTML element:
from datasette_alerts import ConfigElement
def get_config_element(self):
return ConfigElement(
tag="my-config-form",
scripts=["/-/my-plugin/config.js"],
)
Web component contract:
- Inputs:
configproperty,datasette-base-urlattribute,database-nameattribute - Output:
config-changeCustomEvent withdetail: { config: {...}, valid: boolean }
Sidebar Integration
If datasette-sidebar is installed, datasette-alerts automatically registers itself as a sidebar app.
Development
just dev # start dev server
just test # run tests (uv run pytest)
just format # format code (backend + frontend)
just check # lint + type check (backend + frontend)
just frontend # build frontend
just frontend-dev # start Vite dev server
Metadata
Release files for datasette-alerts 0.0.1a11
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| datasette_alerts-0.0.1a11.tar.gz | 150.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| datasette_alerts-0.0.1a11-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 311.6 kB
Release files / datasette_alerts-0.0.1a11.tar.gz
| Download URL | datasette_alerts-0.0.1a11.tar.gz |
|---|---|
| Size | 150.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f31bc687a1a33e642f4e495c08f608e974000753ee7fc44c030d1c50baad9ceb
|
|
BLAKE2b-256 checksum How to use checksums |
f24efe623aeff62cac86898c1baf02324b8e25184ad10fc704c54e4fc94c16b1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 14, 2026.
Transparency logRelease files / datasette_alerts-0.0.1a11-py3-none-any.whl
| Download URL | datasette_alerts-0.0.1a11-py3-none-any.whl |
|---|---|
| Size | 161.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
302329577a8371cbcdce70b3455a80a83f11e4bcae7ffd059c2aa52ebd79a05b
|
|
BLAKE2b-256 checksum How to use checksums |
721ace108d6dfc48469a605621552eb8b351fb6e09a806577e67ab0c00235132
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 14, 2026.
Transparency log