Apple only keeps about four weeks of Screen Time history. This project saves your iPhone and Mac usage into your own database on your Mac, so it's kept for as long as you want, and gives you a dashboard to explore it.
Everything runs locally. Nothing is uploaded anywhere, and Apple's files are only ever read, never changed.
Tested on macOS 14 and macOS 27. This is an independent project, not affiliated with or supported by Apple.
- A Mac (the scripts read Screen Time data stored on macOS)
- Node.js 22.13 or newer (uses the built-in
node:sqlitemodule, so there are no dependencies to install) - Full Disk Access for the app you run the scripts from: System Settings > Privacy & Security > Full Disk Access. Add Terminal (or your editor, if you use its built-in terminal), then quit and reopen it.
- For iPhone data: Share Across Devices turned on in Screen Time on both your iPhone and Mac, signed into the same Apple ID
git clone https://github.com/iccir919/screen-time-tracker.git
cd screen-time-tracker
npm run archive # save your Screen Time data (the first run takes the longest)
npm start # open the dashboard
If the archiver says it can't read Apple's files, Full Disk Access isn't set up yet for the app you're running it from. Add it, then quit and reopen that app.
Then:
- Run
npm run schedule -- installso new data is saved every day (see Run it automatically). Apple deletes Screen Time data after about four weeks, so anything not archived by then is gone.
npm run archive # save new Screen Time data into the archive
npm start # open the dashboard in your browser (Ctrl+C to stop)
npm run report # daily totals and top apps, last 7 days
npm run report -- 14 # last 14 days
npm run lookup # every app with its total time
npm run lookup -- maps # apps matching "maps", day by day
Running the archiver more often is harmless: each run only adds what's new.
npm start runs a small server on your Mac and opens http://localhost:3000. It only listens on this Mac, so nothing is reachable from your network.
The Refresh button at the top saves new Screen Time data (the same as npm run archive) and reloads the page, keeping your filters. Next to it, the dashboard shows when the archive was last updated. If you archive from Terminal instead, just reload the page.
Refresh runs with the permissions of the app you started npm start from. If that's VS Code's terminal or another app rather than Terminal, that app needs Full Disk Access too.
npm start -- --no-open # don't open a browser
PORT=4000 npm start # use another port
It has three pages:
- Today (the home page) has a Day and a Week view, like Apple's Screen Time. The cards show the day and the 7-day average (click either to switch views, in place) and the 30-day average (opens Trends). Day: minutes per hour split by category, above the day through the hours by device, So far today (against that weekday last week and its 6-week average), and the last 8 days. Week: an average day's minutes per hour, above a row for each of the 7 days through the hours (click a day's name to see it), the week's average day added up against the week before, and the 7 days as bars. Beside the charts, Used today (or that day, or this week) shows the apps or categories you used (daily averages in the Week view) and is the page's color key. Clicking one, a block on the timeline, or a piece of an hour's bar, highlights it across the page (a timeline block picks its app, or its category when the list shows categories). While something is highlighted, a bar above the cards says so (every time on the page, cards included, is for it alone); clear it there, by clicking it again, by clicking an empty part of the timeline, or with Esc.
- Trends is about the last 30 days; the arrows move 30 days at a time. Cards show the daily average, the busiest day and the most used app. The category buttons under them spotlight categories in every chart, cards included: the first click shows just that category, more clicks add others, and Show all goes back to everything. Below: Day by day; today against an average day, added up; Patterns: the average day by hour, by weekday or by week; and Top apps, each with its daily average, its share of the time shown, and a small chart of its average day by hour. Clicking a day opens it in Today; clicking an app or its category in Top apps, or a category's piece of a Patterns bar, opens it on the Apps page.
- Follow one category or app on Today or Trends: click it in "Where the time went", the legend, or a grid's row names, and every chart shows just that, with the rest in gray. A bar at the top says what you're following, opens it on the Apps page, and clears it. Click it again to go back. The grids are all shaded the same way: each cell against the busiest one.
- Apps: every app by time per day, for the last 7 days, 30 days or all time, on any or all devices, grouped by category, each category with its time per day and its change from the period before. Each app shows its device, its last 14 days and its change from the period before. Pick a category above the list to see only its apps. Category and app names anywhere on the dashboard open their views. Click an app, or a category's heading, to open its own view: its numbers for the range, a line through its last 6 weeks (with each week's lightest to heaviest day), its average for each weekday, today so far against the same weekday, and when during the week you use it. A category's view also lists its apps, each with its share of the category and its own trend.
- Organize: your categories and app names, with each one's time per day (see below).
Color always means category: the color you pick for each one on Organize. Amounts without a category, like the hours of the week, are in grays.
Averages count only days with data, and leave out today while it's still going, so a morning doesn't pull them down. The cards say which dates they cover, and, when some days have no data, how many do ("27 of 30 days with data").
The pages need no libraries and work without internet. Only the typeface (Plus Jakarta Sans, from Google Fonts) needs it; offline, they use your system font.
Open the Organize page (the link at the top of the dashboard, or http://localhost:3000/organize). Changes save as you make them, and the dashboard uses them the next time it loads.
- Categories: add your own and pick each one's color from 12 (click its color). Each category's ⋯ menu renames it, moves it up or down, or deletes it. Deleting a category moves its apps to Other. Other is always there and always gray. The order here is the order on the other pages.
- Apps: rename any app and pick its category. Apps you haven't set yet are listed first. The Set by column shows whether an app's name and category come from the built-in list or from you, and Reset goes back to the built-in ones.
Common apps start with built-in names and categories (default_apps.mjs). Anything else shows as the last part of its app ID, under Other, until you name it. Your categories and app names are saved in your archive database, not in the project files, so they stay private and survive updates.
npm run schedule -- install # every day at 10:00 PM
npm run schedule -- install 08:30 # or pick a time (24-hour)
npm run schedule -- status # is it scheduled, and how did the last run go
npm run schedule -- run # run the scheduled job now, to test it
npm run schedule -- uninstall # stop running automatically
This uses launchd, the Mac's built-in scheduler. If the Mac is asleep or off at the scheduled time, it runs when the Mac wakes up. Each run archives new data and logs to ~/screen_time_archive/logs/archive.log.
Scheduled runs don't go through Terminal, so Node itself needs Full Disk Access. The install step prints the exact path to add. If you move the project folder or update Node, run install again.
Without the schedule, run npm run archive at least every couple of weeks so nothing ages out before it's saved.
Everything is stored in ~/screen_time_archive/, outside the project folder:
screen_time.db: the archive (SQLite), including your app names and categoriesbiome_raw/: raw copies of Apple's Biome files, kept even after Apple deletes themlogs/archive.log: output from scheduled runs
This is a detailed record of which apps you use and when, so treat it as private: don't commit it, upload it, or attach it to an issue. To start over, delete the folder.
macOS records app usage in a few places, and the archiver reads all of them:
Biome (~/Library/Biome/streams/restricted/) holds Apple's event streams. Each file uses Apple's SEGB container format, and each record inside is a protobuf message. The archiver copies the raw files into the archive, then pairs the events into sessions.
- ScreenTime.AppUsage (macOS 27 and later, this Mac only) matches Apple's own Screen Time numbers most closely.
- App.InFocus marks each app coming on screen and leaving it. It has a
localfolder for this Mac and aremote/<id>folder for each synced device, which is where the iPhone data comes from.
knowledgeC.db (~/Library/Application Support/Knowledge/) is a SQLite database with older-style app usage and records of when the Mac's screen was on. Its synced iPhone data is unreliable: it arrives in batches and can stop entirely.
For each device and day, the most accurate source with data wins: ScreenTime.AppUsage, then App.InFocus, then knowledgeC. A lower source only fills in the parts of the day the better one doesn't cover.
The Screen Time screen in System Settings reads from another store (RMAdminStore), but macOS protects it so that not even an administrator can read it.
- Overlaps: only one app is on screen at a time, so overlapping records are trimmed and no minute is counted twice.
- Midnight: sessions that cross midnight are split across both days.
- Not counted: the lock screen, screen saver, and home screen.
- Control Center: time with Control Center pulled down counts toward the app underneath it, as Apple does.
- Missing end: a session with no "left screen" event is cut off after 4 hours.
- Opens: uses of the same app less than a minute apart count as one open.
- Untracked: time the Mac's screen was on but no app was recorded, for 2 minutes or more. Apple's own Screen Time has this too: its "All Usage" total includes time it doesn't credit to any app. A game in Game Mode is one known cause, and there are others not yet pinned down. It's included in the daily total (as Apple does), shown hatched in the dashboard, and never credited to an app, with the last apps opened before it as a hint. If you leave the screen on and walk away, that time can count as untracked until the display sleeps.
Per-app times have been checked against Apple's Screen Time screen and match to within a few minutes.
Daily iPhone totals can come out lower than Apple's. Apple's iPhone total includes time it doesn't credit to any app (the gray part of its bars), such as some phone calls, and that time isn't in the synced data. Calls that are recorded show up as "Phone calls".
| File | Purpose |
|---|---|
archive_screen_time.mjs |
Reads Biome and knowledgeC and saves new sessions to the archive |
server.mjs |
The local dashboard server and its API |
public/ |
The dashboard pages (HTML, CSS and browser JavaScript): today.html and trends.html (which share dashboard.js and dashboard.css), apps.* and organize.*, plus the shared base.css, common.js and refresh.js (the Refresh button) |
screen_time_report.mjs |
Daily totals and top apps in the terminal |
app_lookup.mjs |
Per-app totals, for checking numbers against Apple |
schedule_archive.mjs |
Sets up the daily automatic run |
default_apps.mjs |
Built-in names and categories for common apps |
screen_time_lib.mjs |
Shared code: paths, device names, source selection, overlap merging, app settings |
tools/ |
Discovery scripts used to work out where Apple stores the data. Useful if a macOS update changes the formats. |
All of these only read data.
| Script | What it shows |
|---|---|
tools/check_screen_time_data.mjs |
What's in knowledgeC and the Biome folders on this Mac |
tools/find_screen_time_store.mjs |
Whether the protected RMAdminStore can be read |
tools/inspect_biome.mjs |
Which app IDs appear in each archived Biome device folder |
tools/dump_biome_records.mjs |
The raw structure of a few records from any Biome stream |
tools/read_app_usage_stream.mjs |
Sessions from the ScreenTime.AppUsage stream (macOS 27) |
tools/inspect_games_db.mjs |
Whether the protected Games.RecentlyPlayed database can be read |
tools/investigate_iphone_day.mjs |
One iPhone day hour by hour, to compare with Apple's hourly bars |
tools/find_deleted_app.mjs |
Looks everywhere for an app's history, including records Apple marked deleted (read-only) |
tools/restore_from_backup.mjs |
Puts back sessions your archive is missing from an older copy of it, after a preview and a backup |
- Unofficial formats. These are Apple's internal files, not a public API. A macOS update could change them, and the scripts would need updating.
- History starts when you start. The first run can only save what Apple still has, about the last four weeks.
- What's archived stays archived. Apple erases records older than about four weeks, and those of apps you delete, inside its own files. The archiver keeps every session it has already saved, and replaces one only when Apple's files have a corrected version of the same time. Each run reports how many sessions it kept that Apple no longer has. (Versions before November 2026 dropped erased sessions;
tools/restore_from_backup.mjscan bring them back from an older copy of your archive.) - Time zones. iPhone sessions from Biome use the Mac's time zone, so days can shift if your phone was in another time zone.
- Older devices. Biome folders from older devices may not decode yet.
- Game Mode hides games from both App.InFocus and ScreenTime.AppUsage. That time shows up as untracked rather than under the game. Untracked time is Mac-only, since the screen-on records come from this Mac.
MIT. See LICENSE.