Open source · Free npm package

Website monitoring
for developers

Playwright-based checks with retries, screenshots, alerts, and HTML reports. Zero config to get started.

Latest npm versionMonthly npm downloadsTypeScript types includedLicense

Up and running in minutes

One config file. Two commands. Your sites are being watched.
# Install
npm install -D @alosha/monitor

# monitor.config.ts
export default {
  checks: [
    { name: 'Homepage', url: 'https://yoursite.com', interval: '5m' },
    { name: 'API',      url: 'https://api.yoursite.com/health', interval: '1m' },
  ],
  notify: { slack: { webhookUrl: process.env.SLACK_WEBHOOK_URL } }
}

# Run once
npx monitor run

# Or watch continuously
npx monitor watch

Everything you need to ship with confidence

Built for developers who want monitoring handled and out of the way.
  • Real browser checks
    Uses Playwright under the hood — catches JS errors, redirects, and anything a headless Chrome would catch.
  • Automatic retries
    Configurable retry attempts before marking a check as failed. No false alarms from flaky networks.
  • Screenshots on failure
    Automatically saves a full-page screenshot whenever a check fails. Know exactly what went wrong.
  • Multi-channel alerts
    Email, Slack, Discord, or any webhook. Get notified where your team already lives.
  • Built-in scheduler
    Run `monitor watch` and each check fires on its own interval — no cron, no external scheduler needed.
  • HTML reports
    Auto-generated report after every run. Open it in your browser, share it with your team.

The "Datadog tax": what hosted synthetics really cost

Why teams move browser-level uptime checks off per-run SaaS pricing and into their own CI and infrastructure.

Per-run pricing punishes you for monitoring well

Hosted synthetic monitoring meters every browser run, so the exact levers that improve reliability — higher frequency, more regions, deeper journeys — are the levers that inflate the bill.

The cost of the hosted-SaaS approach

  • Frequency = cost

    Moving a check from 5-minute to 1-minute intervals multiplies its billed runs by five. Better detection time, five times the bill.

  • Per-region multiplier

    Each location you check from re-bills the same run, so multi-region coverage scales cost linearly with geography.

  • Per-step browser pricing

    Browser tests are billed per scripted step, so a realistic login-to-checkout journey costs more than a single page load.

With Monitor: Monitor runs as a devDependency in your CI or on a small box you already own. Frequency, regions and journey length change how thorough you are — not what you pay. The marginal cost of one more check run is zero.

Your checks and history live in someone else’s product

Synthetic tests authored in a vendor’s recorder — plus the failure history and screenshots — are trapped there. Leaving means re-writing every journey and losing the timeline, which is exactly what keeps the renewal quote climbing.

The cost of the hosted-SaaS approach

  • Trapped scripts

    Journeys built in a proprietary recorder don’t export into anything you can run yourself.

  • History stays behind

    Run history and failure screenshots live in the vendor; churning the subscription loses the record.

  • Switching cost

    Migrating providers means re-authoring every check in a new DSL — so the renewal always wins the build-vs-buy argument.

With Monitor: Checks are plain TypeScript in your repo, versioned alongside the app they test. Results, screenshots and HTML reports are written to your own storage. There is nothing to export and no renewal holding your monitoring hostage.

What 5 browser checks cost per month

Same workload — 5 checks at 5-minute intervals — priced three ways. Hosted figures use Datadog’s published list rate of $12 per 1,000 browser-test runs (~8,640 runs per check per month).

Hosted synthetics — 5 checks · 3 regions$1,555/mo

~129,600 browser runs/mo × $0.012

Hosted synthetics — 5 checks · 1 region$518/mo

~43,200 browser runs/mo × $0.012

Monitor — watch on a $6/mo VPS$6/mo

flat — runs, regions and journeys don’t change it

Production recipes

The monitoring problems real teams hit — solved with the published API.

Fail your CI build when a critical user journey breaks

The problem: A deploy can pass unit tests but still break login or checkout in a real browser — and you find out from a customer.

import { run } from '@alosha/monitor'

const report = await run({
  checks: [{
    name: 'Login flow',
    url: 'https://app.example.com/login',
    steps: [
      { action: 'fill',  selector: '#email',    value: process.env.TEST_EMAIL! },
      { action: 'fill',  selector: '#password', value: process.env.TEST_PASS! },
      { action: 'click', selector: 'button[type=submit]' },
      { action: 'waitForURL', value: '**/dashboard' }
    ],
    assertions: [{ type: 'visible', selector: '[data-test=user-menu]' }]
  }]
})

// Break the build if any check failed.
if (report.failed > 0) process.exit(1)

Why it works: run() drives a real Chromium session through the journey and returns a structured RunReport, so a single exit-code check turns "does login still work?" into a CI gate that blocks the deploy before users ever see the break.

Run synthetic checks on a schedule with no server to babysit

The problem: You want real browser checks every few minutes, but you don’t want to own a box, a cron daemon, or a per-run monitoring bill to get them.

# .github/workflows/synthetics.yml
name: Synthetics
on:
  schedule:
    - cron: '*/15 * * * *'   # every 15 minutes
  workflow_dispatch:          # ...and on demand

jobs:
  monitor:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      # 'monitor run' exits 1 on failure — the run goes red AND
      # the Slack notifier in monitor.config.ts fires.
      - run: npx monitor run
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

Why it works: GitHub schedules and runs the job for you — no server, no cron daemon, no hosted bill. monitor run exits non-zero when any check fails, so the workflow goes red and your config’s Slack notifier alerts at the same moment. (GitHub cron is best-effort, so treat very short intervals as approximate.)

Get a Slack alert with a screenshot the moment a page goes down

The problem: Uptime pings tell you a URL returns 200, not that the page actually rendered — and they rarely show you what the user saw.

import { watch } from '@alosha/monitor'

// Runs continuously, firing each check on its own interval.
await watch({
  checks: [
    { name: 'Homepage', url: 'https://example.com', interval: '1m', maxResponseTimeMs: 2000 },
    { name: 'Checkout', url: 'https://example.com/checkout', interval: '5m' }
  ],
  notify: { slack: { webhookUrl: process.env.SLACK_WEBHOOK_URL! } }
})

Why it works: watch() schedules each check independently and, on failure, captures a full-page screenshot before posting to Slack — so your first signal of an outage is an alert with visual proof, not a support ticket.

Page on-call through PagerDuty when a critical check fails

The problem: A failed checkout at 3am needs to wake someone. Monitor ships Slack, Discord, email and webhook notifiers — but no native PagerDuty integration.

import { run } from '@alosha/monitor'

const report = await run({
  checks: [
    { name: 'Checkout', url: 'https://app.example.com/checkout', maxResponseTimeMs: 3000 }
  ]
})

// No built-in PagerDuty notifier — page on-call yourself from the
// structured report via the Events API v2.
const down = report.results.filter((r) => r.status === 'fail')

await Promise.all(down.map((r) =>
  fetch('https://events.pagerduty.com/v2/enqueue', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      routing_key: process.env.PAGERDUTY_ROUTING_KEY,
      event_action: 'trigger',
      dedup_key: `monitor-${r.name}`,        // collapse repeats into one incident
      payload: {
        summary: `${r.name} is DOWN — ${r.error ?? 'check failed'}`,
        source: r.url,
        severity: 'critical'
      }
    })
  })
))

if (report.failed > 0) process.exit(1)

Why it works: run() returns every check as a structured CheckResult, so mapping failures onto PagerDuty’s Events API v2 is a few lines — a trigger event plus a stable dedup_key that collapses repeat alerts into a single incident. You get real on-call escalation without waiting for a built-in integration.

Built to pass a dependency review

The questions a CTO asks before adding a package to production — answered up front.
Metric / concern What ships
Check enginePlaywright (Chromium)
Data isolationRuns in your CI / infra
Type safetyShips .d.ts (ESM + CJS)
Install modeldevDependency + CLI
AlertingSlack · Discord · email · webhook
Cost modelFlat · $0 per run
LicensingMIT

Start monitoring in minutes

Monitor is free and MIT-licensed. When you need a hosted feature, a custom build, or a fast answer from the person who wrote it, there is a paid path — backed by the founder, not a ticket queue.
  • Custom checks, assertions or notifier integrations on request
  • Help wiring Monitor into your CI/CD pipeline
  • Priority bug fixes and answers straight from the maintainer