Replay or follow a live F1 race on a polished timing screen made of widgets you arrange. Each widget is a different kind of analysis. Built on OpenF1 data. Runs in your browser.
Try it: pitwall.plusminushalf.com. Replays need nothing. Live needs your own OpenF1 account.
- Replays. Every race, sprint, qualifying and free practice session since 2023. A race starts about 5 seconds after you pick it. It downloads into your browser as you watch, so next time it opens at once.
- One timeline for timing, gaps, tyres, pit stops, race control, weather and team radio. Pause, scrub, or play at up to 64×.
- Dashboards. Overview, Strategy and Telemetry are built in. Each one shows any session. Edit one or make your own: move, resize, add or remove widgets. It's saved. Hover a widget to fill the screen with it.
- Race analysis. Gaps to the leader or the car ahead, lap by lap. Lap times per stint, with each stint's trend in seconds per lap. Battles: cars within a second for laps on end, and who passed whom. Pit stops, and whether each undercut worked. Lap compare lays the selected drivers' race laps over each other and marks where one passed another. Click a lap, a battle or a stop to watch it. Pick a range of laps on the rail under the timeline, and every lap chart zooms to it.
- No spoilers. The timeline shows only what you've watched. Safety cars, retirements, penalties and the finish stay hidden until you get there.
- Qualifying has its own screen. Compare up to 4 laps: speed, delta, throttle, brake and gear. See who is fastest in each mini-sector. Replay the laps as ghosts.
- Free practice has the timing screen by best lap, with the clock counting down. Deleted lap times are struck through. Long runs ranks every race simulation (5+ laps on a set) by compound, with its average and how much slower it gets per lap. Fastest laps compares up to 4 laps as qualifying does, each with its tyre and its age.
- Circuits. Each circuit has a page with every session there since 2023. It shows the race lap record, the pole record, who wins most and the last races there, from F1DB. Home lists the season's circuits, the next one first. The season by round is the other tab.
- Circuit history. Five widgets show the earlier races at a circuit: safety cars, passes on track, race pace, time in the pit lane, and every car's tyre strategy. Add them from the picker's Circuit group, or open the Circuit history dashboard. A circuit's page shows them too. Each race takes 7 reads from OpenF1, kept in your browser.
- Forecast. A circuit's page shows the weather for its next weekend, session by session: the sky, the chance of rain, the air temperature and the wind. Home's header gives the next session's chance of rain and temperature. It comes from Open-Meteo, up to 16 days ahead.
- Live. Follow a race, sprint, qualifying or practice session as it happens. Live qualifying uses the race screen for now. It needs an OpenF1 account, connected in Settings.
- Screenshots. Press S on any page. Drag out an area, or click a widget. Shift-click picks several. The shot is copied as a PNG with the logo and a link to that moment and layout.
- Phones get the same widgets in one scrolling column, with the timeline pinned at the bottom. Editing dashboards and screenshots are for desktops.
| Qualifying | Edit the layout |
|---|---|
![]() |
![]() |
Replays need no account. On OpenF1's free tier, a race is fully downloaded in about a minute, and qualifying opens in about 20 seconds. An account makes downloads faster. They also keep working during live sessions, when OpenF1 blocks the free tier. And it's what live mode needs: your browser follows the session with your account.
- Sponsor OpenF1 at openf1.org (€9.90/month). You get an OpenF1 login: an email and a password.
- In Pitwall, open Settings (top right of the home page). Click Connect under "OpenF1 account".
- Sign in in the popup. Stay connected on this device, or lock it behind a passkey.
Pitwall's code never sees your password. A small vault on a separate site (pitwall-auth.garvit.in) holds it and talks to OpenF1. Pitwall only gets the data. Details: vault/README.md.
During a live session OpenF1 refuses requests from browsers. So while one is on, the vault sends the requests made with your OpenF1 token through a small pass-through on its own site (pitwall-auth.garvit.in/openf1/), which forwards them to OpenF1. Your token passes through it and isn't stored or logged. Your password only ever goes from your browser to OpenF1.
The sponsor tier is a personal subscription. Use your own account. Don't share it.
bun install
bun run dev # http://localhost:5173
bun run build # static site in dist/In dev, live comes from a small relay next to the dev server, with your OpenF1 login in .env. It's for your own use. Don't host it for others.
cp .env.example .env # then fill in OPENF1_USERNAME and OPENF1_PASSWORD
bun run liveTo try live as the hosted site does it, through your account in Settings, run the dev server with VITE_LIVE_RELAY=0 and the vault next to it (bun run vault).
CLI ingest, the live simulator and how it all works: docs/development.md.
A widget is a folder in src/widgets/. Its index.tsx default-exports defineWidget({ ... }). It reads the race through widget-kit's hooks. The hooks return data only up to the replay's current time, so a widget can't spoil a race by accident. The one opt-out is useWholeSession().
A whole widget:
// src/widgets/fastest-lap/index.tsx
import { defineWidget, lapTime, Stat, useFastestLap } from "widget-kit";
function FastestLap() {
const lap = useFastestLap(); // the fastest lap so far, never one still to come
return (
<Stat label="Fastest lap" className="h-full justify-center px-3">
<span className="text-sm tabular-nums">{lap ? `${lapTime(lap.duration)} by #${lap.driver}` : "None yet"}</span>
</Stat>
);
}
export default defineWidget({
id: "fastest-lap", // kebab-case, the same as the folder
name: "Fastest lap",
description: "The fastest lap so far, and who set it.", // shown in the widget picker
group: "session", // its heading in the widget picker: session, driver, telemetry, analysis or circuit
version: "1.0.0",
height: 48, // px
width: { min: 8, default: 10, max: 25 }, // percent of the grid's width
sessions: ["race"], // races and sprints
settings: {},
Component: FastestLap,
});- Make a folder in
src/widgets/with anindex.tsxlike the one above. - Pick your hooks. They're all listed in
src/widgetkit/index.ts. Import them from"widget-kit". - Register the widget in
src/grid/builtins.ts: import it and add it to theALLlist. It then shows up under Edit → + Add widget. - Run
bun run lintandbun test src/widgets. The lint checks that a widget imports only React,widget-kitand files in its own folder. - Open a PR.
weather is the smallest real widget. gap-chart, stint-pace, battles and pit-strategy are bigger. Each keeps its logic in a separate file, with tests next to it.
To match the app's look, use widget-kit's UI pieces: Label, Stat, Icon, DriverTag and TyreBadge. See DESIGN.md.
- Timing:
useRunningOrder,usePositions,useDriver(position, gaps, tyres, lap, status),useFastestLap,useBestSectors. - Laps and strategy:
useLaps(sector times, mini-sectors, speed traps),useStints,usePitStops(pit lane and stationary time).useAllLaps,useAllStintsanduseAllPitStopscover the whole field.useLapWindowgives the laps picked on the timeline's rail, for charts by lap. - Telemetry:
useCar(speed, gear, RPM, throttle, brake, DRS),useCarHistory(recent samples),useFrame(car positions every animation frame, for canvas drawing),useLapTrace(one completed lap as a distance-aligned trace, withuseLapGeometryfor the lap length, sector boundaries and corners;deltaSeries,miniSectorsand friends read them). - The race:
useTrackStatus,useNeutralPeriods(SC, VSC and red flag periods),useSectorFlags,useWeather,useFeed(race control, overtakes, pit stops, team radio). - Session:
useDrivers(names, teams, colours),useTrack(outline, pit lane, corners, sectors),useSessionInfo. - The circuit:
useCircuitRaces(the earlier races there: safety cars, stints, passes, pace and pit stops). Widgets in thecircuitgroup read it, and they show on the circuit's page too. - Playback and the widget:
useTime,usePlayback,useSelection,useSettings,useWidgetSize,usePhone(the phone layout).
Not up for writing one? Missing some data? Open an issue. Say what you want to see and why.
Pitwall ships code, not data. Your browser downloads each race from OpenF1. It's processed and stored in your browser only. No server of ours hosts, bundles or stores OpenF1 data. This repo contains none. One thing passes through: during live sessions, requests made with your OpenF1 account go through the vault's pass-through (see above), which hands each answer to your browser and keeps nothing.
OpenF1 data is licensed CC BY-NC-SA 4.0. Non-commercial use only.
Circuit history (lap records, winners, poles) comes from F1DB. It's built into static files before each deploy, not kept in this repo. F1DB is licensed CC BY 4.0. Forecasts go from Open-Meteo straight to your browser, also under CC BY 4.0.
Pitwall is an unofficial fan project. It is not associated with the Formula 1 companies or with OpenF1. F1, FORMULA ONE, FORMULA 1, FIA FORMULA ONE WORLD CHAMPIONSHIP, GRAND PRIX and related marks are trade marks of Formula One Licensing B.V.
The hosted site counts visits with Cloudflare Web Analytics: which pages are viewed (Home, a circuit or a session), plus country, browser and referrer. On Called It it also counts a call being locked (which driver), shared and copied, as page views of paths of their own. It also sends PostHog (EU cloud) the same page views, clicks, errors, and what's used: widgets added, sessions opened and downloaded, screenshots, calls. PostHog keeps a random visitor ID in the browser's local storage so a return visit counts as the same visitor. No cookies, no accounts, no names or emails. Builds you run yourself have none.
Code: GNU AGPL v3.0 (AGPL-3.0-only), © 2026 plusminushalf. Use it, change it, share it. If you distribute a modified version, or run one that others use over a network, publish its source under the same license.
The license covers the code only. Race data falls under OpenF1's license above.


