Open-source project
aree6/LiftShift avatar
aree6/LiftShift

LiftShift: workout analytics that run entirely in your browser

Your workout logs visualized!

521 stars79 forksTypeScriptNOASSERTION

At a glance

What is it?
LiftShift takes workout logs from Hevy, Strong, Lyfta and other apps and turns them into muscle heatmaps, personal record tracking and plateau detection. All the analysis runs client-side in the browser with no account, and the only server component is a thin proxy that forwards API calls to the source apps.
Who is it for?
Adopt LiftShift if you log your training in one of the supported apps and want muscle-level analysis without handing your workout history to a hosted service, since the analysis runs on your device and the backend only proxies API calls. Do not adopt it expecting it to be the system of record for your training, because it reads from exports and syncs rather than replacing the app you already use.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the thing actually does

LiftShift takes your workout logs from Hevy, Strong, or Lyfta and shows you what they actually mean. The readme then asks three questions that frame the whole product.

Which muscles are you neglecting? Is your bench press actually getting stronger, or just wobbling around the same weight? What should you try next session? And then the answer to why this is not just another logging app: your logging app won't answer those questions.

That is the positioning, and it is a good one. A workout tracker records sets. LiftShift is an analysis layer over that record, and the questions it answers are the ones a logging app structurally cannot, because the logging app has the data but not the interpretation.

The privacy model is stated in one line and it is the most important architectural fact about the project. Everything runs locally in your browser. No account needed. Sync your data through the app's API or upload a CSV, and the analysis happens on your device.

And the readme is specific about what the backend does and does not do. The tech stack line says all analytics run client-side, and that the backend only proxies API calls to Hevy and Lyfta.

So the server exists for one narrow reason. Hevy and Lyfta have APIs, but a browser cannot call them directly from another origin without CORS trouble and without exposing credentials in a way that would end up in the page. A proxy solves both. Everything else, the parsing, the normalisation, the charts, the muscle mapping, the scoring, happens on your machine.

The project describes itself as free and open source workout analytics, and the licence is AGPL-3.0, with a LICENSE file and a note that the official deployment is at liftshift.app.

The feature set, and what is genuinely novel in it

The features list is long, and a few items stand out as more than chart rendering.

Muscle heatmaps is an interactive body map that shows which muscles you train and how much. Click any muscle to see the exact exercises that built it. It tracks weekly volume, volume zones described as MEV and MRV thresholds that scale with your training age, and a 0-100 hypertrophy score per muscle, with male and female silhouettes supported.

The thresholds scaling with training age is the interesting part. Minimum effective volume and maximum recoverable volume are not fixed numbers, and a library that hardcodes them gives misleading advice to everyone. Scaling them means the same dashboard gives a beginner and a five-year lifter different advice from the same log.

PR tracking covers seven types of personal record, weight, 1RM, set volume, session volume, reps, weighted reps and distance, in gold for all-time best and silver for one-month best tiers. It also has premature PR detection for big jumps you could not sustain, PR droughts, and your PR hit rate per week.

Plateau detection gives every exercise a status such as Getting stronger, Plateauing, or Taking a dip, with a confidence level based on how many sessions you have logged. When you stall, it tells you what to try: add a rep, bump the weight, deload, or switch up the rep scheme. It also distinguishes static plateaus, where weight and reps are both frozen, from general plateaus, where the trend is flat but you are still varying.

That distinction matters because the two have different fixes, and treating them the same gives bad advice. A static plateau is a genuine stall. A general plateau with variance is often just noise in the data, and the correct response is to change nothing.

Set-by-set feedback is the most granular feature. Open any past workout and it analyses each set across 19 scenarios, such as normal fatigue, weight jumps that were too aggressive, smart back-off sets and AMRAP pushes. Each set gets a badge and a plain-English suggestion for next session, and it tells you whether to stay at your current top weight, increase it, or reduce it.

The rest of the list covers consistency tracking with a GitHub-style heatmap of your training year, a streak counter and an 8-week consistency trend, a Lifetime Progress view with a per-muscle journey from Seedling to Legend across 9 tiers, a Training Manifest dashboard card with a volume voyage chart marking your first, peak and last training days, and nine shareable Flex cards, including an all-time training receipt styled like a shop receipt and volume comparisons of your total lifted against real-world objects.

Data sources, the merge, and the export

The supported apps table is short and the import methods are not uniform, which is worth checking before you commit to a workflow.

Hevy supports login sync, a Hevy Pro API key, or CSV. Strong is CSV upload only. Lyfta is API key or CSV. Motra is Excel or CSV upload. And there is a generic CSV option that auto-detects columns.

So the practical difference is that Hevy and Lyfta can be synced continuously while Strong and Motra are import-only. If you are on Strong, the workflow is export and upload rather than sync, which changes how fresh the analysis is.

Multi-app merge is the feature for people who moved. Switched from Strong to Hevy? Use both? LiftShift normalizes exercise names across sources and merges everything into one dataset, and duplicate sets are detected and skipped.

Exercise name normalisation is the hard part of that, and the reason it is called out separately. Two apps will name the same lift differently, and a merge that treats bench press and bench-press as different exercises silently doubles your bench volume. Skipping duplicate sets is the second half, and without it a re-import of the same month inflates every number on the dashboard.

Calendar filtering applies to everything. Pick any date range, from a single day to the full year, and every chart, metric and calculation updates to just that window, so you can compare training blocks without exporting and reimporting.

The AI export is the unusual one. You export your structured training data in a format built for AI tools, pick from 8 analysis modules such as a junk volume audit, structural balance, joint health check and unilateral balance, choose a timeframe, and paste it into ChatGPT, Claude, or whatever you use.

So the design decision is that the heavy analysis stays in the application and the language model gets a structured, pre-computed brief rather than your raw log. That is the opposite of the usual pattern of dumping data into a chat window, and it is why the export exists as a set of modules rather than as a raw CSV.

The dependency list supports the same picture. papaparse handles CSV parsing, xlsx handles the Excel path, date-fns handles the date handling that the troubleshooting section turns out to be delicate, lz-string compresses the exported data, and posthog-js is the analytics library for the hosted deployment.

Running it locally, and the shape of the deployment

The local development setup is four commands:

bash
git clone https://github.com/aree6/LiftShift.git
cd LiftShift
npm install
npm run dev

That requires Node.js 22 or newer, and the CONTRIBUTING.md has setup details and coding standards. The Docker route is a single command as well, docker compose --env-file .env.docker.example up --build, and then you open http://localhost:3000.

The docker-compose file shows the two-service architecture and explains the networking decisions in comments that are worth reading.

The frontend is Nginx on port 80 proxied to the host on 3000, and it proxies /api/* to backend:5000 for same-origin with no CORS. The comment on the backend service says no host port is published on purpose, because the browser reaches the backend through the frontend Nginx proxy, so host port 5000, often taken by the macOS AirPlay Receiver, can never collide. There are commented-out lines for those who want to debug the backend directly.

That is a small decision with a real consequence. Publishing 5000 would have meant a conflict with AirPlay Receiver on macOS, and the fix chosen was to remove the surface rather than change the port.

The backend healthcheck is a node one-liner that fetches the /api/health endpoint and exits non-zero on a bad status. The frontend service depends on the backend with a service_healthy condition, so the ordering is enforced by Compose rather than by a sleep.

The frontend environment section is worth a note because of the analytics configuration in .env.example. There is a VITE_ANALYTICS_DISABLED flag described as an analytics kill switch, to set to true or 1 to disable all analytics, and a comment that all data is processed client-side and no sensitive data is sent to external servers. There are separate variables for the PostHog public key and host, with a note on using a proxy for the PostHog host.

The tech stack line in the readme is React 19, TypeScript, Vite, Tailwind CSS and Recharts on the frontend, with Node.js, Express and Puppeteer on the backend proxy, and the package.json confirms it. The build uses Vike for the site build, and there is a perf:budget script that checks the bundle budget, which is a sensible thing to have in a project that ships a large client-side analytics bundle.

How the project has grown

The release history tells you how the project has grown, and the release titles are unusually descriptive about it.

v1.5.0 from 2026-09-05 is titled self-host, balance and joint load, which tells you that the self-hosting path and the balance and joint load analysis modules arrived together. v1.4.0 from 2026-04-27 is Combined Data Sources and Clipboard Export, which is the multi-app merge and the AI export described earlier. And v1.3.0 from 2026-02-19 carries no subtitle at all, which suggests it was a general release rather than a single feature.

So the shape over the year is a move from a Hevy-specific visualiser towards a general analysis tool. The package.json description still says a comprehensive workout visualizer that transforms Hevy app CSV exports into powerful insights, which is the original framing, and the feature list and the source table have both outgrown it. That is normal for a project whose scope widened, and the stale description is a small sign of how quickly it happened.

The repository structure follows the same split. There is a frontend/ directory, which holds the React application and its public assets, with the images under frontend/public/images organized into misc/ and steps/ subdirectories, the steps ones being the quick start walkthrough. There is a backend/ directory for the Express proxy. There is a scripts/ directory with the bundle budget checker, a docs/ directory, and a plans/ directory, which is where a project of this shape would keep the roadmap items that are not yet features.

There are also several environment and setup documents at the top level, an .env.example and an .env.docker.example for the two deployment shapes, plus DOCKER.md, DEPLOYMENT.md, GITHUB_SETUP.md and QUICKSTART.md. Having a separate GITHUB_SETUP.md is a reasonable signal about where a project's audience is, since for a self-hostable web application the GitHub Actions workflow that builds and publishes the container image is often the part an operator needs most and understands least.

The repository also has a metadata.json and a .nvmrc, which pins the Node version for the frontend build. The engines field in the package.json asks for node 22 or newer, and the readme says the same, so the two agree.

The last push was on 2026-10-18 and the repository is TypeScript with the author recorded as Areeb. The licence is AGPL-3.0-only, which is worth a moment's thought for a self-hostable application, since it is the copyleft variant with the network clause, so a modified version served over a network must offer its source.

Editorial conclusion

Adopt LiftShift if you log your training in one of the supported apps and want muscle-level analysis without handing your workout history to a hosted service, since the analysis runs on your device and the backend only proxies API calls. Do not adopt it expecting it to be the system of record for your training, because it reads from exports and syncs rather than replacing the app you already use. Before you commit, check the import method for your app, since Strong is CSV-only and Motra is Excel or CSV upload, while Hevy and Lyfta support API sync, and set the app language to English before exporting if you hit the date parsing problem.

Frequently asked questions

What does LiftShift do with my workout data?

Everything runs locally in your browser, with no account needed. You sync your data through the app's API or upload a CSV and the analysis happens on your device. All analytics run client-side and the backend only proxies API calls to Hevy and Lyfta, so no sensitive data is sent to external servers.

Which workout apps can LiftShift import from?

Hevy through login sync, a Hevy Pro API key, or CSV. Strong through CSV upload. Lyfta through an API key or CSV. Motra through Excel or CSV upload. And there is a generic CSV option that auto-detects the columns.

Can I use LiftShift with more than one logging app?

Yes. LiftShift normalizes exercise names across sources and merges everything into one dataset, with duplicate sets detected and skipped. The feature exists for people who switched apps or who train in two of them, and it is enabled in the v1.4.0 release along with the clipboard export.

What is the AI export in LiftShift?

It exports your structured training data in a format built for AI tools. You pick from 8 analysis modules such as a junk volume audit, structural balance, joint health check and unilateral balance, choose a timeframe, and paste the result into ChatGPT, Claude or whatever you use.

Why does LiftShift fail to parse workout dates?

Your source app is probably using a non-English locale. The fix is to switch the app language to English, export again and re-upload. A related issue is a Showing newest history only banner after an API sync, which means the source API capped how far back the sync could go, so lifetime totals, PRs and streaks reflect only partial history and you should import a CSV or Excel export for the complete log.

Official sources

  1. aree6/LiftShift on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/aree6-liftshift.svg)](https://hysenlabs.com/projects/aree6-liftshift)

Was this useful?