How Frigate's Timeline and Event System Works: A Technical Deep Dive

Frigate records every detection—objects, audio, and API-generated events—in a chronological timeline using a three-part architecture consisting of a Python processing thread, a Peewee database model, and TypeScript frontend utilities.

The Frigate timeline and event system provides a complete audit trail of all detection activity in the blakeblackshear/frigate repository. This system captures object movement, zone entries, stationary states, and audio detections, storing them in a queryable format for the web interface and external analytics.

Core Components of the Frigate Timeline Architecture

TimelineProcessor: The Backend Event Consumer

The TimelineProcessor class in [frigate/timeline.py](https://github.com/blakeblackshear/frigate/blob/dev/frigate/timeline.py) runs as a dedicated thread that consumes a multiprocessing queue (timeline_queue). Detection pipelines populate this queue with events that the processor transforms into structured timeline entries.

The processor extracts the camera name, source type, and event state (start, update, or end) from each queue item. It then constructs a base entry containing a timestamp, camera identifier, source ID, and a JSON payload with box coordinates, labels, and confidence scores. Depending on the event state, it creates specialized entries with class_type values such as visible, entered_zone, stationary, active, attribute, or gone.

To prevent data loss, the processor maintains a pre_event_cache that holds timeline entries until their associated event is persisted in the database. This ensures that timeline rows are never orphaned if an event is still being processed.

Timeline Database Model: Flexible SQL Storage

The Timeline model in frigate/models.py defines a lightweight Peewee ORM table with the following schema:

  • timestamp: When the event occurred
  • camera: The camera identifier (e.g., "front_door")
  • source: The detection origin ("tracked_object", "audio", or "api")
  • source_id: Unique identifier for correlation
  • class_type: Semantic meaning of the entry (event state)
  • data: JSON field containing bounding boxes, scores, zones, and attributes

This schema supports time-based queries while keeping the payload flexible through the JSON column, allowing new detection attributes to be added without schema migrations.

Frontend Utilities: Time-Range Chunking

The TypeScript utilities in [web/src/utils/timelineUtil.tsx](https://github.com/blakeblackshear/frigate/blob/dev/web/src/utils/timelineUtil.tsx) handle the display layer. The getChunkedTimeDay function slices a user-requested time interval into hourly TimeRange chunks, handling partial hours at the end of ranges. The findChunkIndex function locates which chunk contains a specific Unix timestamp using half-open intervals, enabling efficient pagination and lazy loading of timeline data in the UI.

How Timeline Entries Are Created and Processed

Processing Detection Events

The TimelineProcessor distinguishes between different event sources through the handle_api_entry method, which processes audio detections and external API events separately from tracked objects. API-generated events create "heard" or "external" entries that are stored immediately without caching, while object tracking events use the pre-event cache mechanism.

Each queue entry processed by the run() method follows this structure:

from multiprocessing import Queue, Event as MpEvent
from frigate.timeline import TimelineProcessor
from frigate.config import FrigateConfig

# Initialize the queue and stop event

timeline_q = Queue()
stop_evt = MpEvent()

# Start the processor thread

processor = TimelineProcessor(config, timeline_q, stop_evt)
processor.start()

# Queue a detection event

timeline_q.put((
    "front_door",                     # camera name

    "tracked_object",                 # source type

    "start",                          # event state

    None,                             # previous data (None for start)

    {
        "id": "obj-123",
        "frame_time": 1685505600.0,
        "box": [0.1, 0.2, 0.3, 0.4],
        "label": "person",
        "score": 0.92,
        "current_zones": [],
        "stationary": False,
        "attributes": {},
        "current_attributes": [],
    },
))

API-Generated Event Handling

Audio detections and external events bypass the standard object tracking pipeline. When the processor receives these via handle_api_entry, it creates entries with source types "audio" or "api" and class types like "heard", storing them directly to the database without the caching logic used for tracked objects.

Querying and Displaying Timeline Data

Backend Database Queries

Applications can query the timeline directly using the Peewee model to retrieve events within specific time windows or filter by camera and event type:

from frigate.models import Timeline
from datetime import datetime, timedelta

# Calculate the time cutoff (last hour)

cutoff = datetime.utcnow() - timedelta(hours=1)

# Query timeline entries for a specific camera

entries = (
    Timeline.select()
    .where(
        (Timeline.camera == "front_door") &
        (Timeline.timestamp >= cutoff)
    )
    .order_by(Timeline.timestamp)
)

for entry in entries:
    print(f"{entry.timestamp}: {entry.class_type} - {entry.data}")

Frontend Time Range Processing

The web interface uses chunked time ranges to optimize queries for large historical datasets. The TypeScript utilities split days into hourly segments:

import { getChunkedTimeDay, findChunkIndex } from "@/utils/timelineUtil";
import { TimeRange } from "@/types/timeline";

// Define a 9-hour time range
const range: TimeRange = { after: 1685505600, before: 1685538000 };

// Split into hourly chunks
const chunks = getChunkedTimeDay(range);

// Find which chunk contains a specific timestamp
const targetTimestamp = 1685512000;
const chunkIndex = findChunkIndex(chunks, targetTimestamp);

console.log(`Timestamp found in chunk ${chunkIndex}`, chunks[chunkIndex]);

The findChunkIndex function uses half-open intervals for all chunks except the last, ensuring that boundary timestamps are assigned unambiguously to the correct time segment.

Summary

  • Frigate's timeline and event system uses a three-tier architecture: Python processing (TimelineProcessor), SQL storage (Timeline model), and TypeScript frontend utilities.
  • The TimelineProcessor consumes a multiprocessing queue, creates typed entries (visible, entered_zone, stationary, etc.), and prevents orphaned records through a pre_event_cache.
  • Database entries store flexible JSON payloads alongside structured metadata (camera, source, timestamp) for efficient time-based queries.
  • Frontend utilities split time ranges into hourly chunks and locate specific timestamps for optimized UI rendering.
  • API-generated events (audio/external) are handled separately from tracked objects with immediate persistence.

Frequently Asked Questions

What types of events does Frigate record in the timeline?

Frigate records tracked object events (person, vehicle, etc.), audio detections, and external API events. Object events include states like entered_zone, stationary, active, visible, and gone. Audio events use the heard class type, while API events can have custom classifications depending on the source.

How does Frigate handle pre-event data to avoid orphaned records?

The TimelineProcessor maintains an in-memory pre_event_cache that stores timeline entries for events that haven't been fully persisted yet. Once the associated event is confirmed and saved to the database, the cached timeline entries are flushed to the Timeline table. This prevents timeline rows from referencing non-existent events if the system restarts or encounters errors during detection processing.

What is the database schema for the Timeline table?

The Timeline table uses a Peewee ORM model with columns for timestamp (datetime), camera (string), source (enum: tracked_object/audio/api), source_id (string), class_type (string describing the event state), and data (JSON). The JSON field contains detection-specific details like bounding box coordinates, confidence scores, zone lists, and attribute dictionaries.

How does the frontend optimize timeline queries for large time ranges?

The frontend uses the getChunkedTimeDay utility to split large time ranges into hourly chunks. This allows the UI to implement pagination and lazy loading, fetching only the data for the currently viewed hour rather than loading entire days at once. The findChunkIndex function quickly locates which chunk contains a specific timestamp, enabling efficient navigation to specific moments in the timeline view.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →