Status
Track the health of your services and display it with $status.
Setup
The status plugin is included in manifest.js with all core plugins, or can be selectively loaded. It activates when manifest.json contains a status block.
<!-- Meta -->
<link rel="manifest" href="/manifest.json">
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/mnfst@latest/lib/manifest.min.js"></script>
Configuration
Add a status block to manifest.json. Each key becomes a top-level status at $status.<name>, rolled up from its underlying signals. A value can be a string (a single probe), an array (several signals rolled up), or an object (a single signal, or an aggregate with options).
manifest.json {
"status": {
"website": "https://example.com",
"docs": ["https://docs.example.com", "https://cdn.example.com"],
"api": {
"url": "https://api.example.com/health",
"degradedAbove": 800
},
"cloud": {
"signals": [
{ "appwriteService": "databases", "label": "database" },
{ "mirror": "appwrite", "label": "upstream" }
],
"rollup": "worst",
"refresh": 30000
}
}
}
A single project can mix sources freely, with the provider behind each signal is inferred from which field is present.
| Field | Signal | Behavior |
|---|---|---|
url |
HTTP probe | Fetches the endpoint; reads reachability and latency. |
feed |
Status feed | Reads a normalized status JSON the endpoint returns. |
static |
Literal | A fixed state — useful for placeholders or manual control. |
appwriteService |
Appwrite | Resolves against your Appwrite endpoint's /health. |
mirror |
Upstream | Reflects a known third party's own status. |
heartbeat |
Dead-man's switch | A job calls $status.beat(key); silence reads as down. |
mcp |
Manifest hosting | Hosted signal for projects published with Manifest AI. |
Probes & Feeds
For a service you own, probe an endpoint directly or read a status feed it publishes.
urlhas the browser fetch the endpoint and infer state from the response (2xxis operational; adegradedAbovelatency threshold marks it degraded). Simplest, but bound by CORS and run per visitor, so it suits same-origin or CORS-enabled endpoints.feedreads a small JSON document your backend produces and maps its state onto the Manifest vocabulary. This avoids CORS and can represent what a probe cannot, such as background workers and queues.
{
"status": {
"api": {
"url": "https://api.example.com/health",
"expect": 200,
"degradedAbove": 800
}
}
}
A network error, timeout, or CORS block resolves a probe to unknown rather than a false outage. The plugin never reports "down" when it simply could not reach the endpoint.
Mirrors
A mirror reflects a third party's own published status. Common providers are built in by name — github, cloudflare, stripe, openai, anthropic, discord, npm, vercel, netlify, appwrite — and any service running an Atlassian Statuspage works by passing its base URL directly.
Mirror examples {
"status": {
"payments": { "mirror": "stripe" },
"ai": { "mirror": "anthropic" },
"custom": { "mirror": "https://status.example.com" }
}
}
States
Every signal and entry resolves to one of these states.
| State | Level | Meaning |
|---|---|---|
operational |
0 | Healthy |
maintenance |
0.5 | Planned downtime |
degraded |
1 | Working but impaired (e.g. slow) |
partial_outage |
2 | Some functionality unavailable |
major_outage |
3 | Down |
unknown |
-1 | No signal yet, or could not be reached |
A feed or mirror may return its own vocabulary (up, down, pass, minor, etc.) — the plugin maps common terms onto the table above.
Labels
$status.label(state) turns a state into a display string (major_outage → "Major Outage"). Labels default to English but localize through the _ui convention — the same mechanism as the datepicker (_ui.date) and colorpicker (_ui.colorpicker). Provide overrides under _ui.status.label in any loaded data source, per locale:
labels.fr.yaml _ui:
status:
label:
operational: Opérationnel
degraded: Dégradé
partial_outage: Panne Partielle
major_outage: Panne Majeure
maintenance: Maintenance
unknown: Inconnu
$status.label(state) — and anything that uses it — then returns the localized string, reactive to the current locale.
Rollup & Options
Aggregate entries accept options alongside their signals.
| Option | Default | Description |
|---|---|---|
rollup |
"worst" |
How children combine: worst takes the most severe, best the least. |
refresh |
30000 |
Poll interval in milliseconds. |
confirmations |
1 |
Consecutive observations required before a state change (debounce). |
staleAfter |
refresh × 3 |
Milliseconds without a known signal before stale becomes true. |
history |
90 |
Number of recent checks retained for the uptime meter. |
By default, blocks with no options set or a bare "website": "https://example.com" will poll every 30 seconds, rolls up worst-first, and goes stale after 90 seconds.
Reading Status
$status.<name> returns a reactive health object. Read whatever you need; every read re-evaluates when the underlying signals update.
| Property | Type | Description |
|---|---|---|
state |
string | Rolled-up state (see States). |
up |
boolean | true only when state is operational. |
level |
number | Severity, 0 (operational) to 3 (major outage), -1 unknown. |
latencyMs |
number | null | Average probe latency, when measured. |
message |
string | null | A human update attached to the state (from a feed or $status.set), or null. |
stale |
boolean | true when no fresh signal has arrived within the stale window. |
updatedAt |
number | Timestamp of the last resolution. |
signals |
array | The underlying inputs, each with its own label and state. |
uptime |
number | null | Percent operational over the retained history window. |
history |
array | Recent observed states, oldest to newest. |
incidents |
array | Open and recent incidents for this service. |
The magic also exposes a few helpers:
| Accessor | Description |
|---|---|
$status.overall |
Worst state across every entry. |
$status.all |
The map of all entries. |
$status.ready |
true once the first resolution pass has run. |
$status.incidents |
All incidents across every service, newest first. |
$status.label(state) |
Human label for a state string (major_outage → "Major Outage"). |
Address a service by name, or iterate $status directly — each item is a health object, so you read it with dot notation just like a $x source.
<p x-text="$status.API.state"></p>
<template x-for="(service, name) in $status" :key="name">
<span x-text="name + ': ' + service.state"></span>
</template>
UI Templates
The plugin gives you state; the presentation is yours. These templates range from a full status page down to a single indicator — each reads the same $status values, including the built-in service.uptime, the service.history meter, and $status.incidents. History accrues live as the plugin polls (or is hydrated from a feed); incidents are logged whenever you post an update with $status.set.
<div class="col gap-10 w-full">
<!-- Headline banner -->
<div class="row items-center gap-3 p-4 bg-page border border-line rounded-lg">
<figure style="size-6 rounded-full"
:class="{
'bg-positive-content':$status.overall==='operational',
'bg-yellow-400':$status.overall==='degraded',
'bg-orange-400':$status.overall==='partial_outage',
'bg-negative-content':$status.overall==='major_outage',
'bg-surface-3':$status.overall==='maintenance',
'bg-surface-3':$status.overall==='unknown'
}"></figure>
<p x-text="$status.overall==='operational' ? 'All systems operational' : 'Some systems are experiencing issues'"></p>
</div>
<!-- Status list -->
<template x-for="(service, name) in $status" :key="name">
<div class="col gap-1">
<!-- Title -->
<div class="row items-center justify-between">
<strong x-text="name"></strong>
<small x-text="service.uptime + '% uptime'"></small>
</div>
<!-- Daily status -->
<div class="row" style="gap:2px;">
<template x-for="(day,i) in service.history" :key="i">
<figure x-tooltip="`${$status.label(day)}`" class="flex-1 h-6 rounded-xs"
:class="{
'bg-positive-content':day==='operational',
'bg-yellow-400':day==='degraded',
'bg-orange-400':day==='partial_outage',
'bg-negative-content':day==='major_outage',
'bg-surface-3':day==='maintenance',
'bg-surface-3':day==='unknown'
}"></figure>
</template>
</div>
</div>
</template>
<!-- Incident history -->
<div class="col gap-4">
<strong>Past Incidents</strong>
<template x-for="inc in $status.incidents" :key="inc.id">
<div class="row items-center gap-3 p-2 border-t border-line">
<small class="min-w-14 capitalize" x-text="inc.name"></small>
<div class="capitalize"><span x-text="inc.message || $status.label(inc.state)"></span> ∙ <small x-text="inc.resolved ? 'resolved' : $status.label(inc.state)"></small></div>
</div>
</template>
<small x-show="!$status.incidents.length" class="text-content-subtle">No incidents reported</small>
</div>
</div>
Manual Control
The magic exposes methods for operators and background systems.
| Method | Description |
|---|---|
$status.set(name, state, message?) |
Force a state and an optional update message, overriding signals. |
$status.clear(name) |
Remove an override and return to signal-derived state. |
$status.refresh(name?) |
Re-resolve one entry, or all when called with no argument. |
$status.beat(key) |
Pulse a heartbeat signal; absence past its window reads as down. |
<button @click="$status.set('API', 'major_outage', 'Investigating elevated error rates')">Report outage</button>
<button @click="$status.clear('API')">Resolve</button>
<small class="capitalize" x-text="$status.API.state"></small>
<p x-show="$status.API.message" x-text="$status.API.message"></p>
Update Messages
A message is a human note attached to a status — the line you post during an incident, like "Investigating elevated error rates". It rides alongside the state and surfaces as $status.<name>.message, or null when there is none. It can be set one of two ways:
- Manually, as the third argument to
$status.set— for operator actions and incident updates:
<button @click="$status.set('api', 'degraded', 'Investigating elevated error rates')">Post update</button>
- From a feed, by returning a message next to the state. The plugin reads both:
status.json {
"state": "degraded",
"message": "Elevated latency in the EU region"
}
Clearing the override (or the feed dropping its message) returns message to null.
Article does not exist
There is no documentation at this path.