A public mirror of the fantasy.premierleague.com site to be run locally. It allows for AI integration with an LLM (chat), as well as data science, and quering ability, to allow for deeper insights not available on the official site.
- Sync Pipeline: Pulls all public FPL data into a local SQLite database idempotently.
- Express API: Read-only JSON endpoints serving player, fixture, gameweek, and team data.
- React Frontend: Dark-themed, glassmorphism UI for browsing players, fixtures, and My Team.
- My Team Sync: Link your real FPL manager account for synced squad and transfer history.
- Advanced Metrics: Includes xG, xA, xGI, xGP, xAP, xGIP, ICT index, tackles, and recoveries.
- Automated Tests: Comprehensive coverage for sync logic, API endpoints, and React components.
- Backend: Node.js 20, Express 5, better-sqlite3 (SQLite)
- Frontend: React 19, Vite 7, Tailwind CSS v4, shadcn/ui, React Router v7, framer-motion, Recharts
- Tooling: TypeScript 5, tsx, Vitest, npm workspaces, concurrently
- Node.js 20+
- npm 10+
git clone <repo-url> fplytics
cd fplytics
npm install
cp .env.example .envRun the sync pipeline to fetch data from the official FPL API. The first run takes ~40 minutes.
npm run syncStart both the API (port 4000) and frontend (port 5173).
npm run devOpen http://localhost:5173 in your browser.
Run these from the repository root:
| Command | Description |
|---|---|
npm run dev |
Start API and frontend together |
npm run dev:api |
Start only the API |
npm run dev:web |
Start only the frontend |
npm run build |
Build both apps for production |
npm run test |
Run all tests |
npm run test:watch |
Run all tests in watch mode |
When running sync commands through npm, use -- to pass options directly to the underlying script. You can safely combine these options (for example, targeting a gameweek while using force).
npm run sync (Public FPL Data)
npm run sync: Standard full sync. Uses hash snapshots to skip fetching unchanged players.npm run sync -- --gameweek 29: Only fetches players involved in gameweek 29 (much faster).npm run sync -- --force: Bypasses the snapshot mechanism to forcefully re-fetch all data and download all assets.npm run sync -- --gameweek 29 --force: Bypasses the snapshot check just for the players in gameweek 29.
npm run sync:my-team (Personal FPL Accounts)
npm run sync:my-team: Refreshes all currently linked FPL manager accounts.npm run sync:my-team -- --gameweek 29: Refresh squad picks for just that gameweek.npm run sync:my-team -- --account 3: Target one specific linked FPL account by its local database ID.npm run sync:my-team -- --email you@example.com: Target one specific linked FPL account by its login email.npm run sync:my-team -- --force: Force-refresh the active snapshot for all accounts.
Example Combination: npm run sync:my-team -- --email test@test.com --gameweek 29 --force
Copy .env.example to .env. Key variables:
PORT(default 4000)DB_PATH(default./apps/api/data/fpl.sqlite)FPL_MIN_REQUEST_INTERVAL_MS(default 3000)VITE_API_BASE_URL(default unset, uses origin +/api)
For My Team account linking, you must set FPL_AUTH_SECRET to a random string.
apps/api/: Express API, sync pipeline, SQLite database (data/fpl.sqlite)apps/web/: React frontend (Vite, Tailwind, custom UI)packages/contracts/: Shared TypeScript types for API and frontend
For more details, see: