aw-watcher-web: giving ActivityWatch the details a window title cannot
Browser watcher for ActivityWatch
At a glance
- What is it?
- aw-watcher-web is a cross-browser WebExtension that feeds ActivityWatch the active tab title, URL, audible state and incognito state, which desktop window watchers cannot see. It is a small TypeScript project with a Make-driven build and a real deployment catch: Firefox enterprise deployment needs a fork because of the Mozilla add-on policy.
- Who is it for?
- aw-watcher-web is the right choice if you run ActivityWatch and want per-tab detail instead of a window title, and if you are prepared to build it yourself and load an unsigned archive into Chrome or Firefox Developer Edition. It is the wrong tool for a managed fleet that needs Firefox deployment without a fork, or for a Safari deployment that lacks an Apple Developer account, since both are blocked by requirements the project cannot satisfy on its own.
- Can I use it commercially?
- Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 1 day 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 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap between a window title and a tab URL
ActivityWatch collects what you do on a computer, and the quality of that record depends on which watchers are running. The documentation for this extension is blunt about the limit of the desktop side: without a browser watcher, ActivityWatch can usually only see the browser window title. A window title gives you an application and, often, the page name as the site chose to render it. It does not give you the URL, so two pages with the same title are indistinguishable, and a single page app looks identical to every other page in the same tab.
The extension closes that gap. It records four things about the active tab: the title, the URL, the audible state, and the incognito state. The audible state is the part that carries the most judgement, because it distinguishes a tab you are actually reading or listening to from a tab left open in the background. The incognito state matters for a different reason: it tells the collector that the activity happened in a private window, which is a distinction most desktop watchers cannot make.
The project is deliberately narrow in scope. It is not a standalone time tracker, and it does not replace the desktop watchers. It is the browser half of a larger system, and it exists so that the data ActivityWatch already stores is more precise than it would otherwise be.
A small TypeScript codebase built by Vite and a polyfill
The source lives in src/ and is written in TypeScript, with the compiler configured through tsconfig.json. The build tool is Vite, wired up by vite.config.ts, and the browser specific parts are handled by vite-plugin-web-extension, a plugin that knows how to emit a browser extension from a web application build. That pairing is why the same source can produce a Chrome bundle, a Firefox bundle and a Safari bundle: the target browser is chosen by an environment variable at build time rather than by separate codebases.
The runtime dependencies are short. webextension-polyfill provides a single API surface across browsers, which is what lets one codebase target Chrome, Firefox and Safari. deep-equal and p-retry are utility dependencies, and aw-client is the client library that talks to an ActivityWatch instance. The important detail about aw-client is that it is not a version from a package registry. The dependency points at a git URL with a pinned commit hash, 6093fbb10c2721510515c93ae7f85b9bc6210eda, on the aw-client-js repository. A pinned commit is reproducible, but it also means an upgrade is a deliberate edit to package.json rather than a routine version bump, and the lock file, package-lock.json, must be regenerated with it.
Development tooling is equally plain: TypeScript 5.7 for checking, Prettier 3 with a configuration of no semicolons and single quotes, and a .pre-commit-config.yaml for formatting on commit. The Makefile is the entry point for everything a user actually runs.
Building the extension with make and loading it unpacked
The build requires Node.js 23 or higher, Git and Make. The clone has to bring submodules with it, because the media directory is a separate repository that the Makefile expects to line up with the Vite output:
git clone --recurse-submodules https://github.com/ActivityWatch/aw-watcher-web.git
cd aw-watcher-webIf you clone without the submodule flag you will find the media content missing later, when the build produces an empty or broken asset directory.
Dependencies are installed with a single make target, which runs npm ci and therefore installs exactly the versions in package-lock.json:
make installThe two release builds differ only in the browser variable they pass to Vite, and each one also produces a zip in the artifacts directory:
# For Firefox:
make build-firefox
# For Chrome:
make build-chromeYou should end up with artifacts/firefox.zip or artifacts/chrome.zip. Loading the Chrome build as an unpacked extension takes four steps: extract the zip to a folder, open chrome://extensions, enable Developer mode, then use Load unpacked and pick the extracted folder. For Firefox you go to about:addons, open the gear icon, choose Install Add-on From File, and select artifacts/firefox.zip. Firefox will refuse an unsigned extension unless you are running Developer Edition or Nightly and have set xpinstall.signatures.required to false in about:config. A dev build target also exists that runs Vite in watch mode, and a compile target that type-checks without emitting.
Development builds come from GitHub Actions as unsigned archives
There are two ways to get the code onto a browser without building it yourself. The first is the official route, the Chrome Web Store and Firefox Add-ons listings, which carry the released version. The second is the development build published as workflow artifacts on GitHub Actions.
The download procedure is short: open the latest successful workflow run, scroll to the Artifacts section, and download either firefox.zip or chrome.zip. Two constraints come with it. A GitHub login is required to fetch artifacts, which closes the door on automated or anonymous distribution of a development build. And the builds are unsigned, so the browser will treat them as developer mode or developer edition content and the user has to accept that when loading them.
That is the same limitation you accept with a local build, but without the build step. For a person evaluating the extension on a personal machine, the artifact route is faster than cloning and running make. For a team, it inverts the usual trust model: you would be running a binary from a workflow run rather than from a signed store listing, and the artifact is not versioned in any way you can pin.
Firefox enterprise deployment is blocked by the add-on policy
This is the sharpest edge in the project, and it is a policy problem rather than a technical one. The documentation states that due to the Mozilla Add-on Policy, enterprise deployment is not possible with the Mozilla hosted versions of the extension, and that you need to fork the extension and change a hardcoded value to make it work. The same policy is why a privacy notice must be displayed, and the mechanism for pre-accepting it is the Firefox Enterprise Policy template.
The policy file names the extension by its internal identifier and grants offline data collection consent:
{
"policies": {
"3rdparty": {
"Extensions": {
"{ef87d84c-2127-493f-b952-5b4e744245bc}": {
"consentOfflineDataCollection": true
}
}
}
}
}Read the rule carefully. The policy is third party, because the extension in the store is not the one Mozilla hosts for your organisation, and the identifier is pinned to a specific build. If the extension is forked, that identifier is the hardcoded value you change, and the policy file has to be rewritten to match the fork. In other words, managed deployment of this extension in Firefox is a fork, a policy edit, and a rebuild, not a download.
The Chrome path has no equivalent obstacle in the documentation, since an unpacked or store build can be pushed through enterprise policy settings by an administrator.
The Safari route ends in Xcode and an Apple Developer account
Safari support is described as a conversion step rather than a first-class build target, and the source confirms that distinction. The Makefile has a build-safari target alongside the Chrome and Firefox ones, and it sets VITE_TARGET_BROWSER to safari for the Vite build, so the extension is generated in the standard directory first. The conversion then uses the macOS tool:
make install
make build-safarixcrun safari-web-extension-converter ./buildXcode opens automatically once the conversion finishes. In Xcode you select the macOS build target, build the project, run the extension, and then enable it under Safari, Settings, Extensions, where it appears as aw-watcher-web.
The stated requirements are strict. Xcode must be installed, the extension has to be signed with your own Apple Developer account, and Safari extensions require macOS 11.0 or later. The signing requirement is the one that decides whether this path is available to you at all, since a developer account and a macOS machine are both prerequisites that the Chrome and Firefox builds do not need.
Where a desktop watcher is the better answer
The honest comparison here is with the other half of the system. A desktop window watcher sees the browser window and reports its title, which means it works on any application, not only on a browser, and it keeps recording when you are not looking at a web page at all. A document open in a word processor, a terminal, an email client: a desktop watcher covers that, and a browser extension cannot.
The browser extension is the opposite case. It applies only to tabs, it reports a URL and an audible state that a window title cannot express, and it knows whether the tab is in an incognito window. Running both gives you the union. Running only the extension gives you a partial record of a workday and nothing about the applications that were never a browser tab.
There is a second practical difference. A desktop watcher is a process on the machine you are tracking, while a browser extension runs inside the browser, which means it inherits the browser's own extension permissions and the browser's own update model. The extension data can be pulled apart from the rest of the ActivityWatch record at the source, which is the point of the per-tab fields, but it also means the extension can only see what the browser API exposes.
Release cadence, the pinned client, and what MPL-2.0 means for a fork
The repository is not archived and the last push was on 2026-10-22. The release history is sparse but not dead: version 0.6.0 was published on 2026-09-08, while version 0.5.2 and version 0.5.0 both date from 2025, on 2025-03-11 and 2025-02-25. A gap of more than a year between 0.5.0 and 0.6.0 is the signal to read carefully if you are planning a long lived deployment, and the project publishes no separate homepage to point you at release notes.
The dependency on a pinned aw-client commit deserves attention on upgrade. Pinning is the right call for reproducibility, and npm ci enforces it, but it also means an aw-client fix or a new API does not reach you until someone edits package.json and regenerates package-lock.json. Treat that as a planned maintenance task rather than a routine dependency bump.
The licence is MPL-2.0, and it interacts with the fork requirement in a way worth understanding before you start. MPL-2.0 is a file level copyleft licence: modifications to MPL covered files must be released under MPL-2.0, while combining that code with separately licensed work in the same directory does not force the whole project into that licence. Because a Firefox enterprise deployment needs a fork, this is the scenario where the terms matter most, and the LICENSE file in the repository is the text that governs it. That is a licensing note, not legal advice, and the point to act on is that a fork is a distribution decision with obligations attached.
Editorial conclusion
aw-watcher-web is the right choice if you run ActivityWatch and want per-tab detail instead of a window title, and if you are prepared to build it yourself and load an unsigned archive into Chrome or Firefox Developer Edition. It is the wrong tool for a managed fleet that needs Firefox deployment without a fork, or for a Safari deployment that lacks an Apple Developer account, since both are blocked by requirements the project cannot satisfy on its own. Check three things before you start: that your Node version is 23 or higher, that the media submodule and the pinned aw-client commit resolve in your clone, and whether the Chrome Web Store or Firefox Add-ons release of version 0.6.0 already covers the browsers you need to cover.
Frequently asked questions
What does aw-watcher-web record for ActivityWatch?
It records the active tab's title, URL, audible state and incognito state. The documentation notes that without a browser watcher, ActivityWatch can usually only see the browser window title from desktop window watchers.
How do I build aw-watcher-web from source?
You need Node.js 23 or higher, Git and Make, then clone with git clone --recurse-submodules and run make install followed by make build-firefox or make build-chrome. The zips are written to artifacts/firefox.zip and artifacts/chrome.zip, and the Chrome build can be loaded unpacked from chrome://extensions with Developer mode enabled.
Can aw-watcher-web be deployed to Firefox through enterprise policy?
Not with the Mozilla hosted versions, because of the Mozilla Add-on Policy. The documentation says you need to fork the extension and change a hardcoded value, and a privacy notice can be pre-accepted with a third party Firefox Enterprise Policy entry using the extension identifier.
What licence is aw-watcher-web released under?
The repository is licensed under MPL-2.0, with the licence text in the LICENSE file. The project publishes releases including version 0.6.0 on 2026-09-08, and the last push to the repository was on 2026-10-22.
Official sources
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.
[](https://hysenlabs.com/projects/activitywatch-aw-watcher-web)Was this useful?