Skip to content
English
  • There are no suggestions because the search field is empty.

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

  1. 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.
  2. 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

  3. 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:

  1. Right-click the Toggl extension icon β†’ "Open side panel" on the same tab Claude is driving.
  2. 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