Build a Toggl alternative

Build a Toggl alternative as a start button, a weekly view, and honest totals. $9–$18 a person a month is rent you can cancel after one real week.

  • getting-started
  • vibe-coding
  • scope

You can build a Toggl alternative as a start button, a weekly view, and a total you trust. Toggl Track is that product. The catalog line is “One button when work starts. Honest answers about where the week went.”

You pay $9–$18 a person a month for it. That is $108–$216 a year for every person on the team. The basic replacement is 8 hours: a one-click timer, manual entries, and a weekly view.

I would ship this if I still rented a timer to remember what Tuesday was for. It is marked Weekend. The verdict on the card is “Track effort, not people”.

Last time I wrote about building a Mailchimp alternative. That was a teardown. This one is a regular backlog topic.

What you need to build a Toggl alternative

The Toggl Track page splits the work into three rows.

The first row is the one you want this weekend. A one-click timer with a description, a project, and a task. Manual entries when you forgot to press start. A weekly view that adds those rows up. Tags can wait until you have two kinds of work on the same project.

The second row is 3 days. Projects, tags, estimates, team reports, and a CSV. Those can wait until one real week already has a total you trust.

The third row is a month and up. Native desktop and mobile apps, integrations, billing. The table labels that row as the part you should skip.

If you tell an agent to “clone Toggl”, it aims at the third row. Copy the prompt, then pin it to the first one.

Here is the shape of the data. Nothing else has to exist at 8 hours.

const member = {
  id: 'U-flavio',
  timezone: 'Europe/Rome',
}

const writing = {
  id: 'proj-writing',
  name: 'October writing',
}

const posts = {
  id: 'task-posts',
  projectId: 'proj-writing',
  name: 'Blog posts',
}

const running = {
  id: 'te-wed-0930',
  ownerId: 'U-flavio',
  projectId: 'proj-writing',
  taskId: 'task-posts',
  note: 'Draft the Toggl post',
  startedAt: '2026-10-28T07:30:00.000Z',
  stoppedAt: null,
}

You start the timer at 09:30 in Rome. The row stores UTC. stoppedAt stays null until you hit stop, or start the next task.

START THE TIMER October writing, blog posts UTC start, timezone on the member SWITCH TASKS Old stop equals new start One running timer per person OPEN THE WEEK Timed rows plus typed hours Totals from stored durations Read Wednesday as 90 minutes Running timers stay out of exports
The 8-hour v1 is a start, a switch, and a week that adds up. Native apps stay off.

How does Toggl work

I am describing the catalog’s model here, not reverse-engineering Toggl. A time tracker is a person, a running interval, and a list of stopped ones. Reports add the stopped ones.

The screens on the page are a timer, a calendar and timesheet, a project list, and reports. For 8 hours you can live with the timer and the week view. Estimates and team filters sit on the 3-day row.

The data model is TeamMember, Project, Task, Tag, TimeEntry, ProjectEstimate. This weekend you need TeamMember, Project, Task, and TimeEntry. ProjectEstimate belongs on the 3-day row.

The capability line is Astro, HTMX, and D1. This site is an Astro app on Cloudflare, so that is the path I default to. Node.js plus Astro is the catalog default. Use TanStack Start if React is your weekday language.

Every query is scoped by the member. Only the owner edits their time by default. A guessed entry id from another account returns nothing: no project name, no note, no 404 that confirms the slug. A project lead can later read assigned projects. Every admin correction stores actor, before, after, and a reason.

Who a Toggl alternative is for

The audience on the page is a product team of up to 10 people tracking effort across a handful of projects.

The goal is to capture time with almost no friction and compare it with project estimates, without surveillance. This weekend, that means one running timer and the projects you already have.

If you need native apps, a billing export, or a desktop tracker watching the team, keep paying. The site puts that rebuild at a month on purpose.

The same v1 also replaces Clockify if what you wanted was a start button you own.

Hours that later become an invoice belong in Harvest, $13–$17.50 a person a month, about 4 hours for a timer and a weekly sheet. The Harvest guide is that walk.

One running timer

The first mini-prompt on the page is “The timer that refuses to run twice”.

Each person has at most one running timer. Starting a new entry must stop the current one at the same instant, so the old stoppedAt equals the new startedAt.

Enforce that in the database, not only in the button handler. A partial unique index on running entries per user, or a transaction that stops then starts and fails on conflict. Two near-simultaneous start requests must produce one running timer and one stopped entry.

async function startTimer(db, ownerId, next) {
  return db.transaction(async (tx) => {
    const current = await tx.getRunning(ownerId)
    const now = new Date().toISOString()

    if (current) {
      await tx.stopEntry(current.id, now)
    }

    return tx.insertEntry({
      ...next,
      ownerId,
      startedAt: now,
      stoppedAt: null,
    })
  })
}

Duration is elapsed seconds between two UTC instants. You never recompute it from a local clock.

function elapsedSeconds(entry) {
  const start = Date.parse(entry.startedAt)
  const stop = Date.parse(entry.stoppedAt)
  return Math.round((stop - start) / 1000)
}

elapsedSeconds({
  startedAt: '2026-10-28T07:30:00.000Z',
  stoppedAt: '2026-10-28T09:00:00.000Z',
})
//5400

Ninety minutes is 5400. Store that from the instants. The week view can format it as 1:30.

A timer left running for 14 hours because someone closed the laptop should be flagged on next load. Suggest a stop time. Never silently discard it.

Midnight, DST, and typed hours

The second mini-prompt is “Midnight, DST, and the entry that lives on two days”.

Store start and stop in UTC plus the member’s named timezone. The calendar and daily totals use local boundaries. An entry from 11pm to 1am shows on both local days, one hour each, while the stored duration stays two hours.

Spring-forward has 23 hours. Fall-back has 25. Daily totals follow wall-clock slots. The weekly total still equals the sum of stored durations. Never store a local time as the source of truth.

Manual entries are duration-only rows. Someone types 1.5 on Thursday because they forgot to start the timer. Those rows have a localDate and a duration, and no startedAt. Overlap checks apply only to timed entries.

The week view mixes both kinds in the same cell. Editing a cell that holds one typed row can update it in place. A cell backed by a timed entry, or by more than one row, opens a breakdown instead of overwriting the fragments.

The third mini-prompt, “The overlap fixer”, lives next to that grid. When someone drags an edge or types a time, check the new interval against every other timed entry they own. Reject negative and zero-length intervals. For a collision, offer trim, split, or replace in one transaction.

What not to build in a Toggl clone

Leave out invoicing, payroll, automated activity monitoring, timesheet approvals, and native apps.

The non-goal on the page already names those. You are replacing the timer you currently rent.

Surveillance is the trap. The guardrail says never capture screenshots, keystrokes, app activity, or location. Members control their own timers, and every admin edit is visible in history. Paste that sentence when the agent starts a “see what the team is doing” panel.

Invoicing belongs in FreshBooks, $23–$60 a month, about 10 hours for clients, invoices, and a PDF. The FreshBooks walkthrough is that post. Paste a weekly total into a line item by hand.

If the agent starts a desktop tracker or a payroll export, stop it and paste the skip row again.

8-HOUR V1 One-click start and stop Manual entries and a week view UTC instants, local day totals SKIP THIS HALF Native desktop and mobile apps Integrations and billing Activity monitoring
Ship the 8-hour timer. Leave apps, billing, and surveillance on the skip row.

Toggl alternative or Harvest

Harvest is the same start button with a billable second job. $13–$17.50 a person a month. 4 hours for a running timer, a weekly timesheet, and a CSV. The verdict is “Very buildable”.

Harvest’s card wants clients, rates, and later a submitted week. Toggl’s first row is looser: start, stop, a week you can read. If your pain is “I cannot remember Tuesday”, start here. If your pain is “Ada needs an invoice from these hours”, start on the Harvest card, then paste the total into FreshBooks.

Always-on capture is a different product. RescueTime is $7–$12 a month, about 3 days for local app durations and a daily report. It watches the foreground app on one machine, with a pause control. Do not bolt that onto Daytrace.

I would not start here as a first vibe-coded app. The first-project post still points at a tonight card. This is the weekend after you have shipped one small tool.

The 3-day row, after one real week

Use the timer for a week as the real log, not as a demo. Then look at the second estimate.

Projects with members, tasks, and an effort estimate. Progress and remaining hours from the same durations the week view uses. An overrun warning when tracked time passes the estimate.

Reports a project lead can filter by date, project, task, person, and tag. A summary table, a detailed list, and a CSV that agree. The fourth mini-prompt is “The CSV that can’t lie or execute”. Compute all three from one normalized duration. Exclude still-running timers, or two exports a minute apart will disagree. Escape fields that start with =, +, -, or @, so a note like =HYPERLINK(...) opens inert in Excel.

The catalog says 3 days for that row. Skip the month-long native app and the billing platform.

The scope post is the same three-row table if you want the habit, not just this card. The prompt post is the extra line that says do not watch the team’s screens.

Host it, time a real week, cancel

The product pages put hosting this way: a focused replacement costs your build time plus close-to-zero hosting.

This one wants Astro, HTMX, and D1, because that is the stack on the card. There is a table for entries, so take a backup before you delete the paid account.

Put the timer on a hostname you already own, hours.flaviocopes.com. Add the two projects you already work on. Start a timer. Switch to a second task. Type Thursday’s forgotten hour. Open the week. Confirm the stored durations, the local day slots, and the weekly total still match a from-scratch sum.

If you miss estimates or a CSV, add them from the 3-day row.

Then cancel Toggl Track, or Clockify, whichever one you pay for.

Start now

Go to Build your own Toggl Track. Copy the Astro prompt, or TanStack Start if that is what you write.

Tell the agent the first estimate only: one-click timer, manual entries, weekly view. Eight hours.

Start one real task this afternoon. Stop it when you stand up.

Keep reading

Read all articles →