PitLane's overlay system is designed to be extensible. You can create your own overlays using HTML, CSS, and JavaScript (or React), and PitLane will discover, serve, and feed them live race data alongside the built-in overlays.
How overlays work
Every overlay is a web page served by PitLane's built-in server on localhost:9100. Overlays receive live race data over a WebSocket connection and render it however they want. OBS picks them up as browser sources.
The built-in template overlays (leaderboard, lower-third, battle, session-info, ticker) are simple single-file HTML pages — good starting points for your own overlays.
Creating a basic overlay
1. Create a folder
Create a folder in PitLane's overlays directory with your overlay's ID as the folder name. Inside, create an index.html and a manifest.json.
your-overlay/
index.html
manifest.json
2. Write the manifest
The manifest.json tells PitLane about your overlay:
{
"id": "your-overlay",
"name": "My Custom Overlay",
"author": "Your Name",
"version": "1.0",
"description": "A brief description of what this overlay shows.",
"tags": ["community"],
"icon": "Tv",
"controls": []
}
Required fields:
id— unique identifier, must match the folder namename— display name shown in PitLane's overlay panelcontrols— array of control definitions (can be empty)
Optional fields:
author,version,description— metadata shown in the overlay info dialogtags— categorization:"template","broadcast", or"community"icon— a Lucide icon name for the overlay panel. Available icons: List, LayoutList, User, UserRound, Swords, Info, MessageSquare, Timer, Gauge, Flag, Trophy, Radio, Tv, BarChart3, Map, Layers, Zap, Eye, Activity, Clock
3. Connect to live data
In your index.html, connect to PitLane's WebSocket to receive live race data:
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
background: transparent;
font-family: sans-serif;
color: white;
}
</style>
</head>
<body>
<div id="overlay"></div>
<script>
const ws = new WebSocket('ws://localhost:9100/live');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
// data.cars — array of all cars
// data.focusedCar — the currently spectated car
// data.battles — active battles
// data.events — recent race events
// data.session — session info (weather, flags, time)
// data.overlayControls['your-overlay'] — your control values
render(data);
};
function render(data) {
// Your rendering logic here
}
</script>
</body>
</html>
Set background: transparent so your overlay layers cleanly over the game capture in OBS.
4. Add it to PitLane
- In PitLane, go to the Overlays panel
- Click Add Overlay — PitLane scans for new overlays on the filesystem
- Your overlay should appear in the list
- Add it to your active set and toggle it on
5. View it in OBS
Add a browser source in OBS pointing to http://localhost:9100/overlay/your-overlay.
Adding controls
Controls let users configure your overlay from PitLane's interface without editing code. Add them to the controls array in your manifest.
Control types
Toggle — on/off switch
{
"id": "show-gaps",
"type": "toggle",
"label": "Show Gaps",
"placement": "inline",
"default": true
}
Select — dropdown with predefined options
{
"id": "sort-mode",
"type": "select",
"label": "Sort By",
"placement": "inline",
"options": ["position", "gap", "last-lap"],
"default": "position"
}
Range — numeric slider
{
"id": "font-size",
"type": "range",
"label": "Font Size",
"placement": "settings",
"min": 12,
"max": 32,
"step": 2,
"default": 16
}
Color — color picker
{
"id": "accent-color",
"type": "color",
"label": "Accent Color",
"placement": "settings",
"default": "#0EA5E9"
}
Text — text input
{
"id": "header-text",
"type": "text",
"label": "Header Text",
"placement": "settings",
"default": "Race Standings",
"maxLength": 50,
"placeholder": "Enter header text"
}
Button — trigger a momentary action
{
"id": "reset-animation",
"type": "button",
"label": "Reset Animation",
"placement": "inline",
"duration": 1000
}
Placement
"inline"— shown directly in the overlay's expanded controls in the panel"settings"— shown in the overlay's settings dialog (gear icon)
Reading control values
Control values are included in the WebSocket data:
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
const controls = data.overlayControls['your-overlay'] || {};
const showGaps = controls['show-gaps'] ?? true;
const sortMode = controls['sort-mode'] ?? 'position';
};
WebSocket data reference
Every message from the WebSocket includes:
| Field | Type | Description |
|---|---|---|
cars | array | All cars with position, lap times, speed, pit status, driver info, and more |
focusedCar | object or null | Detailed telemetry for the currently spectated car (gear, pedals, RPM, fuel) |
battles | array | Active battles with the two cars involved and the gap between them |
events | array | Recent race events (overtakes, incidents, pit stops, fastest laps) |
session | object or null | Session info: weather, flags, time remaining, track length |
overlayVisibility | object | Whether each overlay is toggled on or off |
overlayControls | object | Current control values for each overlay, keyed by overlay ID |
theme | object | Global theme: primaryColor, secondaryColor, panelOpacity |
timestamp | number | Current timestamp |
Development workflow
For active development with hot reload:
npm run dev:overlay -- your-overlay
This starts a Vite HMR dev server on port 5174 that proxies to PitLane's overlay server. CSS and JavaScript changes are reflected instantly without refreshing OBS.
This workflow is available if you're developing overlays within the PitLane source tree (e.g., as a React overlay in src/overlays/). For simple HTML overlays, PitLane's overlay server already reads files from disk with no caching — just edit and refresh.
Starting from a template
The 5 template overlays (leaderboard, lower-third, battle, session-info, ticker) are single HTML files designed to be forked. Copy one to a new folder, add a manifest.json, and start modifying.