# How Incident Reports Are Generated and Managed in Uptime Kuma

> Discover how Uptime Kuma generates and manages incident reports using Socket.io events. Learn about create, edit, delete, resolve, and history features optimized for your status page.

- Repository: [Louis Lam/uptime-kuma](https://github.com/louislam/uptime-kuma)
- Tags: internals
- Published: 2026-02-28

---

**Uptime Kuma manages incident reports through Socket.io events that validate and persist incident beans to the database, supporting create, edit, delete, resolve, and paginated history operations via the status page interface.**

Incident reports in Uptime Kuma provide real-time communication about system outages and maintenance directly on public status pages. According to the louislam/uptime-kuma source code, the platform implements a complete incident lifecycle through database-backed beans, Socket.io handlers in the backend, and a Vue.js frontend interface. This article explores the technical architecture behind how incidents are created, validated, resolved, and retrieved with cursor-based pagination.

## Incident Storage and Data Model

Uptime Kuma treats an incident as a database record stored in the **`incident`** table, utilizing RedBean ORM for persistence operations.

### The Incident Bean Structure

The `Incident` class defined in **[`server/model/incident.js`](https://github.com/louislam/uptime-kuma/blob/main/server/model/incident.js)** extends `BeanModel` and provides two critical methods for managing incident state:

```javascript
class Incident extends BeanModel {
    async resolve() {
        this.active = false;
        this.pin = false;
        this.last_updated_date = R.isoDateTime(dayjs.utc());
        await R.store(this);
    }

    toPublicJSON() {
        return {
            id: this.id,
            style: this.style,
            title: this.title,
            content: this.content,
            pin: !!this.pin,
            active: !!this.active,
            createdDate: this.created_date,
            lastUpdatedDate: this.last_updated_date,
            status_page_id: this.status_page_id,
        };
    }
}

```

The `resolve()` method marks incidents as inactive and unpins them from the status page, while `toPublicJSON()` sanitizes the bean for client consumption, exposing only necessary fields including `id`, `title`, `content`, `style`, and timestamps.

### Validation Logic

Before any persistence operation, the `validateIncident` function in **[`server/socket-handlers/status-page-socket-handler.js`](https://github.com/louislam/uptime-kuma/blob/main/server/socket-handlers/status-page-socket-handler.js)** enforces strict requirements:

```javascript
function validateIncident(incident) {
    if (!incident.title || incident.title.trim() === "") {
        throw new Error("Please input title");
    }
    if (!incident.content || incident.content.trim() === "") {
        throw new Error("Please input content");
    }
}

```

This validation ensures that both title and content fields contain non-empty strings before the server processes creation or update requests.

## Server-Side Incident Lifecycle via Socket.io

All incident-related actions are performed through Socket.io events handled in **[`server/socket-handlers/status-page-socket-handler.js`](https://github.com/louislam/uptime-kuma/blob/main/server/socket-handlers/status-page-socket-handler.js)**, which interact with the database through RedBean ORM operations.

### Creating and Editing Incidents

The **`postIncident`** event handles both creation and updates. When the frontend emits this event with an incident payload, the handler validates the data and either updates an existing row (if an `id` is supplied) or dispenses a new `incident` bean:

- Sets `pin=true` and `active=true` for new incidents
- Associates the incident with the target status page via `status_page_id`
- Persists the bean using `R.store`

The **`editIncident`** event allows modification of existing records. It looks up the incident by `incidentID` and `status_page_id`, then permits updates to `title`, `content`, `style`, and `pin` fields. The system restricts style values to a specific set: `info`, `warning`, `danger`, `primary`, `light`, or `dark`.

### Resolving and Deleting Incidents

Resolution occurs through the **`resolveIncident`** event, which delegates to the model's `resolve()` method:

```javascript
// Server-side resolution logic
await incidentBean.resolve();   // marks inactive and unpins

```

This sets `active=false`, `pin=false`, and updates the `last_updated_date` field before persisting changes.

Deletion is handled by the **`deleteIncident`** event, which locates the incident by ID and status page, then permanently removes it using `R.trash(bean)`.

### Paginated History Retrieval

The **`getIncidentHistory`** event implements cursor-based pagination for archived incidents. It delegates to `StatusPage.getIncidentHistory(statusPageID, cursor, isPublic)` in **[`server/model/status_page.js`](https://github.com/louislam/uptime-kuma/blob/main/server/model/status_page.js)**:

```javascript
static async getIncidentHistory(statusPageId, cursor = null, isPublic = true) {
    // Query fetches incidents older than the supplied cursor
    const total = await R.count("incident", " status_page_id = ? ", [statusPageId]);
    
    const lastIncident = incidents[incidents.length - 1];
    let nextCursor = null;
    let hasMore = false;

    if (lastIncident) {
        const moreCount = await R.count(
            "incident",
            " status_page_id = ? AND created_date < ? ",
            [statusPageId, lastIncident.created_date]
        );
        hasMore = moreCount > 0;
        if (hasMore) {
            nextCursor = lastIncident.created_date;
        }
    }

    return {
        incidents: incidents.map(i => i.toPublicJSON()),
        total,
        nextCursor,
        hasMore,
    };
}

```

This approach uses `created_date` as the cursor, returning objects containing `incidents`, `total`, `nextCursor`, and `hasMore` for efficient frontend pagination.

## Frontend Implementation in StatusPage.vue

The primary UI for managing incidents lives in **[`src/pages/StatusPage.vue`](https://github.com/louislam/uptime-kuma/blob/main/src/pages/StatusPage.vue)**, which communicates with the backend through Socket.io emissions.

### Creating a New Incident

When administrators submit the incident form, the component emits the `postIncident` event with the status page slug and incident object:

```javascript
postIncident() {
    this.$root.getSocket().emit(
        "postIncident",
        this.slug,
        this.incident,
        (res) => {
            if (res.ok) {
                this.incident = {}; // reset form
                this.loadIncidents(); // refresh list
            } else {
                this.$toast.error(res.msg);
            }
        }
    );
}

```

### Editing and Resolving Incidents

The component similarly handles modifications through `editIncident` emissions, and resolution through `resolveIncident`:

```javascript
// Editing example
const edited = {
    id: incidentId,
    title: "Database outage – resolved",
    content: "Failover completed; services normal.",
    style: "info",
    pin: false,
};

this.$root.getSocket().emit("editIncident", this.slug, incidentId, edited, callback);

// Resolving example
this.$root.getSocket().emit("resolveIncident", this.slug, incidentId, callback);

```

### Fetching Paginated History

The history tab utilizes `getIncidentHistory` with cursor-based navigation:

```javascript
function fetchHistory(cursor = null) {
    this.$root.getSocket().emit(
        "getIncidentHistory",
        this.slug,
        cursor,
        (result) => {
            if (result.ok) {
                this.history = result.incidents;
                this.nextCursor = result.nextCursor;
                this.hasMore = result.hasMore;
            }
        }
    );
}

```

## Summary

- **Incident reports** in Uptime Kuma are stored as beans in the `incident` table, managed through the `Incident` class in [`server/model/incident.js`](https://github.com/louislam/uptime-kuma/blob/main/server/model/incident.js).
- **Socket.io events** (`postIncident`, `editIncident`, `deleteIncident`, `resolveIncident`) handle all CRUD operations in [`server/socket-handlers/status-page-socket-handler.js`](https://github.com/louislam/uptime-kuma/blob/main/server/socket-handlers/status-page-socket-handler.js).
- **Validation** requires non-empty title and content strings before database persistence.
- **Resolution logic** uses the `Incident.resolve()` method to mark beans inactive and unpinned.
- **Pagination** is cursor-based, implemented in `StatusPage.getIncidentHistory()` using creation dates for navigation.
- **Frontend integration** occurs through Vue.js components in [`src/pages/StatusPage.vue`](https://github.com/louislam/uptime-kuma/blob/main/src/pages/StatusPage.vue) that emit socket events and handle paginated responses.

## Frequently Asked Questions

### How are incident reports stored in Uptime Kuma?

Incident reports are stored as database records in the `incident` table using RedBean ORM. The `Incident` class in [`server/model/incident.js`](https://github.com/louislam/uptime-kuma/blob/main/server/model/incident.js) provides methods like `resolve()` for state management and `toPublicJSON()` for data sanitization, ensuring that only appropriate fields are exposed to clients.

### What Socket.io events handle incident management?

The backend listens for five primary events in [`server/socket-handlers/status-page-socket-handler.js`](https://github.com/louislam/uptime-kuma/blob/main/server/socket-handlers/status-page-socket-handler.js): `postIncident` (create/update), `editIncident` (modification), `deleteIncident` (removal via `R.trash`), `resolveIncident` (marking inactive), and `getIncidentHistory` (paginated retrieval). Each event validates input and associates operations with specific status pages through `status_page_id` parameters.

### How does pagination work for incident history?

The system implements cursor-based pagination through the `getIncidentHistory` event. It uses `created_date` as the cursor field, querying for incidents older than the supplied cursor value. The response includes `nextCursor`, `hasMore`, and `total` count, allowing the frontend to efficiently navigate large incident histories without offset-based performance degradation.

### What validation is required when creating an incident?

The `validateIncident` function requires both `title` and `content` fields to contain non-empty strings after trimming whitespace. If validation fails, the server throws an error before any database operations occur. Additionally, the `style` field is restricted to specific values (`info`, `warning`, `danger`, `primary`, `light`, `dark`) during edit operations.