Create a Toggl 2.0 Browser Integration (with Claude)
π€ This guide shows how to author a Toggl 2.0 custom browser integration β the in-page Toggl "Track" button on a third-party site β using Claude instead of hand-writing the JSON. Worked example below: a GitLab issue board.
What you're building
A Toggl 2.0 integration injects the Toggl 2.0 button into a third-party website (GitHub, Jira, GitLab, Asanaβ¦) and teaches it how to read a task title + metadata from that page's DOM. An integration is a single JSON document. Claude drives the whole authoring loop β finding selectors, injecting the button on the live page, verifying placement β so you don't write selectors by hand.
Prerequisites
- Claude Code (any project folder works β the work happens in the browser, not the repo).
- The Toggl 2.0 browser extension installed in Chrome and signed in, with a workspace/organization available.
- The Claude-in-Chrome extension connected (Claude drives the page through it).
- Recommended: for the session, enable only Claude-in-Chrome and the Toggl extension β disable others. Extensions like 1Password can silently intercept clicks/inputs Claude drives.
Step 0 β Install the skill (one time)
Install the Toggl skills globally so Claude knows the integration-authoring flow:
npx skills add toggl/skills
The CLI detects your installed agents and symlinks the skill into ~/.claude/skills/. This makes the toggl-browser-integrations skill available. Source: https://github.com/toggl/skills
Once installed, the skill triggers automatically when you ask Claude to add the Toggl button to a website.
Step 1 β Open the target page & ask Claude
- In Chrome, open the exact page/item you want the button on (e.g. a GitLab board, an issue detail, a list view) β already authenticated and in the right state.
- In Claude Code, ask it to build the integration, e.g.:
Create a Toggl browser integration on this page:
https://gitlab.com/<group>/<project>/-/boards - Claude selects your Chrome browser, navigates/confirms it's on the tab you see (it injects a banner + checks the tab is visible/focused), and inspects the page DOM β thinking about the page structure and probing elements for stable anchors and the text to track.
π‘ It works from any folder β you only need Claude Code running plus the skill and the browser. Nothing is committed to your repo.
Step 2 β Answer Claude's intent questions
Before writing JSON, Claude asks what it can't reliably infer:
- What to track as the task title? β e.g. issue number + title (
#43198 Auto DevOps callout bannerβ¦). - Any extra metadata? β project, labels, milestoneβ¦ or none (just the title).
- Single vs. one-per-card β Claude usually infers this from how many times the anchor matches (a board β one button per card; a detail view β one button). It states its inference; you only correct it if wrong.
In the worked example we chose #number + title and no metadata.
Step 3 β Open the Toggl side panel
Claude can't click browser chrome, so this part is you:
- Right-click the Toggl extension icon β "Open side panel" on the same tab Claude is driving.
- In the panel header ("Saving to"), pick an organization.
β οΈ The bridge is per-tab: the side panel must be open on the exact tab Claude is posting to, with an org selected β otherwise nothing injects. Initially the panel shows no integration for the site; that's expected (Claude authors from scratch with fresh IDs).
Step 4 β Claude injects & verifies
Claude posts the definition to the page (via a shared-DOM bridge), which applies it locally and injects the button(s) immediately. Claude then verifies automatically (takes ~1β2 min):
- Counts the injected wrappers (e.g. 10/10 cards on the board).
- Confirms each button reads its own card's text (per-card scoping).
- Hit-tests that the button is actually clickable β boards often have a "click anywhere to open" overlay covering the card, and visible β clickable.
- Screenshots/zooms to check alignment and theming.
Step 5 β Tweak placement (iterate)
If the spot isn't right, just say so β e.g. "put it on the bottom-right of each card." Claude reworks the anchor and adds any CSS needed to beat the card's click-overlay (giving the button its own stacking context with isolation: isolate), reloads, re-posts, and re-verifies. Repeat until it's where you want it and reads correctly.
Step 6 β Test it for real
Open one of the items, click the injected Track button, confirm the tracked title is correct (e.g. #number + description), start tracking, then stop it. Confirm it behaves end-to-end.
Step 7 β Save (or keep local)
- Local draft: what Claude applied is a local draft β injected on this tab and it survives a page refresh, but isn't synced anywhere.
- Save to org: click Save in the side panel to persist the integration to your organization (needs permission). This enables it for everyone in the org.
π One integration can cover multiple pages. The board and the issue-detail view are different button placements in the same definition. You initially build for one surface; just continue the conversation ("also add it on the issue detail page") and Claude adds the extra button. Don't force one button to serve two surfaces.
Worked example recap β GitLab boards
| Aspect | Result |
|---|---|
| Site | gitlab.com issue board |
| Buttons | One per card (multiple: true) β 10/10 cards |
| Tracks | #<number> <title> (e.g. #43198 Auto DevOps callout bannerβ¦) |
| First placement | Inline after the title link |
| Final placement | Bottom-right of each card footer (.board-card-footer, append last) |
| Overlay handling | isolation: isolate β’ z-index so the button beats GitLab's card click-overlay |
| Theme | Follows page light/dark via themeDetector |
{ "name": "GitLab Boards", "domain": "gitlab.com", "selectors": { "card": "[data-testid=\"board-card\"]" }, "resolvers": { "idText": { "closest": "@card", "query": "[data-testid=\"work-item-id-text\"]" }, "title": { "closest": "@card", "query": "[data-testid=\"board-card-title-link\"]" } }, "buttons": [{ "name": "card-button", "anchor": ".board-card-footer", "append": "last", "multiple": true, "variant": "minimal", "iconSize": 14, "description": { "resolvers": ["idText", " ", "title"] } }], "cssContent": "[data-toggl-button-wrapper=\"card-button\"]{position:relative;isolation:isolate;z-index:1;margin-left:auto;align-self:center;}", "themeDetector": { "colorElement": "body" } }
Tips & gotchas
- Prefer stable selectors β
data-testid, roles, ARIA β over hashed CSS-module class names, which change between deploys. - List vs. detail are different buttons β separate entries with their own
anchor,multiple, and scoping. - Stretched click-overlays β cards that are "click anywhere to open" can cover your button even when it looks on top; the fix is a separate stacking context, not just a bigger
z-index. - If nothing injects β check the side panel is open on this tab, an org is selected, and only Claude + Toggl extensions are enabled.
- Per-tab bridge β keep the side panel and Claude on the same visible tab.
Reference
- Skill repo: https://github.com/toggl/skills