Skip to main content
RapidDev - Software Development Agency
lovable-integrationsEdge Function Integration

How to Integrate Lovable with Clockify

Integrating Clockify with Lovable requires Edge Functions to proxy the Clockify REST API. Store your Clockify API key in Cloud Secrets, create an Edge Function that reads workspaces, projects, and time entries and starts or stops timers, and build custom time tracking interfaces in Lovable's React frontend. Clockify offers a generous free tier with unlimited users, making it ideal for teams who want professional time tracking without per-seat costs — and its clean API is one of the easiest to work with.

What you'll learn

  • How to generate a Clockify API key and store it in Cloud Secrets
  • How to build a Deno Edge Function that reads Clockify workspaces, projects, and time entries
  • How to start and stop the Clockify timer from a Lovable interface
  • How to build a team timesheet dashboard aggregating hours by project and user
  • How to generate time reports filtered by date range, project, and user
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate14 min read35 minutesProductivityMarch 2026RapidDev Engineering Team
TL;DR

Integrating Clockify with Lovable requires Edge Functions to proxy the Clockify REST API. Store your Clockify API key in Cloud Secrets, create an Edge Function that reads workspaces, projects, and time entries and starts or stops timers, and build custom time tracking interfaces in Lovable's React frontend. Clockify offers a generous free tier with unlimited users, making it ideal for teams who want professional time tracking without per-seat costs — and its clean API is one of the easiest to work with.

Why integrate Clockify with Lovable?

Clockify has become the default free time tracker for freelancers, small agencies, and distributed teams precisely because it removes the biggest friction point of most tools: unlimited users at no cost. The free tier includes full time tracking, project management, reporting, and team features — paid tiers add scheduling, capacity management, and advanced invoicing. Over 5 million users track time in Clockify, and for many teams it serves as the single source of truth for hours worked.

Building a custom Lovable integration with Clockify is particularly useful for teams that want time tracking embedded in their own tools rather than in a separate Clockify tab: an operations dashboard that shows real-time project hours alongside task completion rates and budget data, a project management tool that displays time tracked against each task from Clockify without requiring a context switch, a client portal that shows verified hours logged to client projects, or a team capacity view that combines Clockify's time data with calendar availability.

Clockify's REST API v1 is well-documented and uses the X-Api-Key header for authentication — a simple, reliable scheme. The API is workspace-scoped: most endpoints require a workspace ID as a path component, and your API key must be a member of that workspace. Time entries are the core resource: each entry has a start time, end time, description, project association, and optional task association. The timer is a special resource — the currently running time entry for a user. This tutorial covers all the essential patterns from API setup through building production-ready timesheet and timer components.

Integration method

Edge Function Integration

Clockify has no native Lovable connector. All Clockify API calls use the REST API v1 at api.clockify.me/api/v1, proxied through Supabase Edge Functions. API keys are stored in Cloud Secrets and sent as X-Api-Key headers. Edge Functions read workspaces, projects, and time entries, and support timer start/stop — returning clean time tracking data to your Lovable frontend for custom dashboards and team timesheets.

Prerequisites

  • A Lovable project with Cloud enabled
  • A Clockify account (free tier is sufficient for API access)
  • A Clockify API key — generate at clockify.me/user/settings → API → Generate API key
  • Your Clockify workspace ID — found in the URL when logged into Clockify (clockify.me/workspaces/WORKSPACE_ID) or via the API
  • Project IDs for the projects you want to track — found in Clockify URLs or via GET /workspaces/{workspaceId}/projects

Step-by-step guide

1

Generate your Clockify API key and store credentials in Cloud Secrets

Clockify API keys are personal credentials tied to your user account — they provide access to all workspaces you're a member of. To generate one, log into Clockify at clockify.me and click your profile photo in the top-right corner. Select 'Profile Settings', then scroll to the 'API' section at the bottom of the page. Click 'Generate' to create an API key (or 'Regenerate' if one already exists). Copy the key — it won't be shown again after you navigate away, though you can regenerate if needed. You also need your workspace ID. The easiest way to find it is from your browser URL when using Clockify — it's the long string in clockify.me/workspaces/WORKSPACE_ID/. Alternatively, you can retrieve it via the API after setting up your credentials. In your Lovable project, open the Cloud tab by clicking '+' at the top of the screen, navigate to Secrets, and click 'Add secret'. Add two secrets: CLOCKIFY_API_KEY with your API key, and CLOCKIFY_WORKSPACE_ID with your workspace ID. Like Everhour, Clockify uses a custom X-Api-Key header rather than Bearer token — your Edge Function will set this header on every request. Clockify is notably one of the only major time tracking platforms with a fully functional API on the free tier.

Pro tip: If you don't know your workspace ID yet, you can find it by calling GET https://api.clockify.me/api/v1/workspaces with just the API key — this returns all your workspaces with their IDs. Store the correct workspace ID in Cloud Secrets after confirming.

Expected result: CLOCKIFY_API_KEY and CLOCKIFY_WORKSPACE_ID are stored in Cloud Secrets with masked values.

2

Create the Clockify proxy Edge Function

Clockify's API v1 is hosted at api.clockify.me/api/v1 and follows RESTful patterns with workspace-scoped endpoints. All project and time entry operations require the workspace ID in the path. The timer is managed via the /workspaces/{workspaceId}/user/{userId}/time-entries endpoint — to start a timer, POST a new time entry without an end time; to stop it, PATCH the current running entry with an end time. The Edge Function below implements an action-based router covering the most common Clockify operations. Note the time entry endpoint structure: listing time entries uses GET /workspaces/{workspaceId}/user/{userId}/time-entries (user-scoped), while creating a timer uses POST to the same endpoint, and the reports endpoint uses a different base URL (reports.api.clockify.me) for detailed aggregated reporting. The function handles both API bases.

Lovable Prompt

Create a Supabase Edge Function at supabase/functions/clockify-proxy/index.ts. Read CLOCKIFY_API_KEY and CLOCKIFY_WORKSPACE_ID from Deno.env.get(). Set X-Api-Key header on all requests. Handle CORS. Accept POST with action and params. Implement: 'get_workspace' (GET /workspaces/{workspaceId}), 'list_projects' (GET /workspaces/{workspaceId}/projects with optional params.archived filter), 'list_users' (GET /workspaces/{workspaceId}/users), 'list_time_entries' with params.userId, params.start and params.end ISO strings (GET /workspaces/{workspaceId}/user/{userId}/time-entries with start and end query params), 'start_timer' with params.userId, params.projectId, params.description (POST /workspaces/{workspaceId}/time-entries with start = now ISO string, no end), 'stop_timer' with params.userId (PATCH /workspaces/{workspaceId}/user/{userId}/time-entries with end = now ISO string), 'get_current_timer' with params.userId (GET /workspaces/{workspaceId}/user/{userId}/time-entries?in-progress=true), 'add_time_entry' with all fields (POST /workspaces/{workspaceId}/time-entries with start, end, projectId, description). Return all responses.

Paste this in Lovable chat

supabase/functions/clockify-proxy/index.ts
1// supabase/functions/clockify-proxy/index.ts
2import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
3
4const BASE = "https://api.clockify.me/api/v1";
5const corsHeaders = {
6 "Access-Control-Allow-Origin": "*",
7 "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
8};
9
10serve(async (req) => {
11 if (req.method === "OPTIONS") {
12 return new Response("ok", { headers: corsHeaders });
13 }
14 const apiKey = Deno.env.get("CLOCKIFY_API_KEY");
15 const workspaceId = Deno.env.get("CLOCKIFY_WORKSPACE_ID");
16 if (!apiKey || !workspaceId) {
17 return new Response(JSON.stringify({ error: "Clockify credentials not configured" }), {
18 status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" },
19 });
20 }
21 const authHeaders = { "X-Api-Key": apiKey, "Content-Type": "application/json" };
22 const ws = workspaceId;
23 const { action, params = {} } = await req.json();
24 let resp;
25 const now = new Date().toISOString();
26
27 switch (action) {
28 case "get_workspace":
29 resp = await fetch(`${BASE}/workspaces/${ws}`, { headers: authHeaders });
30 break;
31 case "list_projects": {
32 const q = new URLSearchParams({ archived: String(params.archived ?? false) });
33 resp = await fetch(`${BASE}/workspaces/${ws}/projects?${q}`, { headers: authHeaders });
34 break;
35 }
36 case "list_users":
37 resp = await fetch(`${BASE}/workspaces/${ws}/users`, { headers: authHeaders });
38 break;
39 case "list_time_entries": {
40 const q = new URLSearchParams();
41 if (params.start) q.set("start", params.start);
42 if (params.end) q.set("end", params.end);
43 q.set("page-size", String(params.pageSize || 100));
44 resp = await fetch(`${BASE}/workspaces/${ws}/user/${params.userId}/time-entries?${q}`, { headers: authHeaders });
45 break;
46 }
47 case "get_current_timer":
48 resp = await fetch(`${BASE}/workspaces/${ws}/user/${params.userId}/time-entries?in-progress=true`, { headers: authHeaders });
49 break;
50 case "start_timer":
51 resp = await fetch(`${BASE}/workspaces/${ws}/time-entries`, {
52 method: "POST", headers: authHeaders,
53 body: JSON.stringify({
54 start: now,
55 projectId: params.projectId,
56 description: params.description || "",
57 billable: params.billable ?? true,
58 }),
59 });
60 break;
61 case "stop_timer":
62 resp = await fetch(`${BASE}/workspaces/${ws}/user/${params.userId}/time-entries`, {
63 method: "PATCH", headers: authHeaders,
64 body: JSON.stringify({ end: now }),
65 });
66 break;
67 case "add_time_entry":
68 resp = await fetch(`${BASE}/workspaces/${ws}/time-entries`, {
69 method: "POST", headers: authHeaders,
70 body: JSON.stringify({
71 start: params.start, end: params.end,
72 projectId: params.projectId,
73 description: params.description || "",
74 billable: params.billable ?? true,
75 }),
76 });
77 break;
78 default:
79 return new Response(JSON.stringify({ error: `Unknown action: ${action}` }), {
80 status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" },
81 });
82 }
83
84 const data = await resp.json();
85 return new Response(JSON.stringify(data), {
86 status: resp.status, headers: { ...corsHeaders, "Content-Type": "application/json" },
87 });
88});

Pro tip: Clockify time entries use ISO 8601 format with timezone offset for start and end times (e.g., 2026-03-30T09:00:00Z). Always use UTC (Z suffix) for consistency — Clockify stores and returns all times in UTC, converting to user local time only in the UI.

Expected result: The Edge Function deploys. Calling action 'list_projects' returns your Clockify projects. Calling 'list_users' returns all workspace members with their user IDs.

3

Build a timer widget and time entry display

The timer component is the centerpiece of most Clockify integrations — it allows users to start and stop time tracking without leaving your Lovable app. The key UI elements are: a running timer display (showing elapsed time), a project selector, and start/stop controls. The elapsed time is calculated client-side from the timer's start time rather than making API calls every second, keeping the API call count low while maintaining accuracy. On component mount, call get_current_timer to check if a timer is already running. If one is, display it immediately with the elapsed time counted from its start time. This handles the case where a user started tracking in Clockify's native app or another interface and now opens your Lovable app — the integration stays synchronized. Below the timer, a time entries list shows the day's logged entries with project names, descriptions, and durations.

Lovable Prompt

Create a ClockifyTimer component that accepts a userId prop. On mount, call action 'get_current_timer' to check for a running timer. If running, show the project name, description, elapsed time counting up from the timer's start ISO string. Add a Stop button calling action 'stop_timer'. When no timer is running, show a project selector (from action 'list_projects'), a description input, and a Start button calling action 'start_timer'. Below the timer widget, show today's time entries from action 'list_time_entries' with today's date range. Display each entry: project name with colored dot, description, start-end times, and duration formatted as Xh Ym. Show daily total at the bottom. Add a Manual Entry form with start/end time pickers for logging past sessions.

Paste this in Lovable chat

Pro tip: Calculate elapsed timer time client-side: const elapsed = Date.now() - new Date(timerStartISO).getTime(). Update every second with setInterval. This avoids API polling and keeps the display smooth. Clear the interval when the component unmounts to prevent memory leaks.

Expected result: The timer widget shows the correct running/stopped state on load. Start/stop controls create and close time entries in Clockify. The daily entries list shows all entries for today with accurate durations.

4

Build a team timesheet view and reporting

Beyond personal timer control, Clockify shines for team timesheet management. Fetching time entries for all workspace users across a date range gives you the raw data to build any aggregated view: weekly timesheets, project hour summaries, billable vs non-billable breakdowns, and utilization reports. The time entries include a billable boolean, project ID, task ID, and duration in ISO 8601 duration format (e.g., PT2H30M for 2 hours 30 minutes). For larger teams or longer date ranges, consider caching time entry data in Supabase to avoid re-fetching on every page load. Store a daily snapshot at end-of-day and use the cached data for historical views, only fetching live data for the current day. RapidDev's team can help design this caching layer for teams with complex reporting requirements across many projects and users.

Lovable Prompt

Build a ClockifyTeamTimesheets component. Add a date range picker (default: this week Mon-Sun). Fetch all workspace users via action 'list_users'. For each user, fetch time entries for the date range via action 'list_time_entries'. Build an aggregate view: a table with users as rows and days of the week as columns. Each cell shows total hours logged that day. Parse Clockify's ISO duration format (PT2H30M → 2.5 hours). Color-code cells: white for 0 hours, light blue for 1-6 hours, green for 6-8 hours, amber for 8-10 hours, red for over 10 hours. Show totals row and totals column. Add a 'By Project' toggle that instead shows users as rows and projects as columns. Export CSV functionality.

Paste this in Lovable chat

Pro tip: Clockify returns time entry duration in ISO 8601 duration format (PT2H30M). Parse it with a regex: const match = duration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/); then combine hours, minutes, seconds into total decimal hours.

Expected result: The timesheet renders all team members with correct hour totals per day. Color coding accurately reflects hours logged. The By Project toggle pivots the view. CSV export downloads the complete timesheet.

Common use cases

Embedded timer and time log in a project management tool

A team uses a custom Lovable project tracker with Supabase and wants to add Clockify time tracking directly — clicking a task starts the Clockify timer for the associated project, and clicking again stops it and logs the entry. A per-task time summary shows total hours logged across all team members without leaving the task management interface.

Lovable Prompt

Add Clockify time tracking to the task list component. Each task row shows a play/stop button and a today's hours badge. Clicking play for a task calls my Clockify Edge Function to start a timer for that task's associated Clockify project ID (stored in tasks.clockify_project_id). Show a live elapsed time counter on the active task row updating every second. Clicking stop logs the entry. Below the task title show 'Logged today: Xh' by fetching time entries for that project today. Retrieve the current timer state on component mount to restore the play/stop state if a timer was already running.

Copy this prompt to try it in Lovable

Team timesheet dashboard for weekly review

A project manager wants a weekly view of all team members' logged hours, broken down by project, to compare against estimates and identify over-allocated team members. The Edge Function fetches time entries for all workspace users for the selected week, and the frontend renders a pivot table showing hours per person per project with daily breakdowns and weekly totals.

Lovable Prompt

Build a weekly timesheet page at /timesheets. Add a week selector (Mon-Sun). Fetch all users in the workspace and time entries for the selected week from my Clockify Edge Function. Render a table: rows are team members, columns are Mon through Sun plus a Total column. Cell value = total hours logged that day. Clicking a cell opens a detail panel with that day's individual time entries. Add a column for each project showing project-level totals per person. Color cells red if under 6 hours on a workday. Export to CSV button. Show a totals row at the bottom and a totals column on the right.

Copy this prompt to try it in Lovable

Project profitability tracker with hourly rates

A freelance studio uses Clockify to track project hours and wants a profitability dashboard that multiplies hours by each team member's hourly rate to calculate project revenue vs estimated budget. The Edge Function fetches time entries per project with user breakdowns, and the frontend multiplies by configurable rates stored in Supabase to generate margin calculations.

Lovable Prompt

Build a project profitability dashboard at /profitability. Fetch all active Clockify projects from my Edge Function. For each project, fetch total time entries this month grouped by user. Multiply each user's hours by their hourly rate (stored in a Supabase user_rates table keyed by Clockify user ID). Calculate: total revenue (hours * rate sum), project budget from Supabase projects table, gross margin, and hours remaining before budget exceeded. Display in a sortable table with color-coded margin indicators. Click a project to see the full breakdown by team member.

Copy this prompt to try it in Lovable

Troubleshooting

API returns 403 Forbidden even though the API key looks correct

Cause: The X-Api-Key header is being set incorrectly — either using 'Authorization: Bearer' instead of 'X-Api-Key', or there's a typo in the header name. Clockify uses a custom header, not the standard Authorization header.

Solution: Verify your Edge Function is setting the header as 'X-Api-Key' (with hyphen, exact capitalization). Do not use 'Authorization: Bearer' or 'Api-Key' — Clockify's API specifically requires 'X-Api-Key' as the custom header name. Check Cloud → Logs to see the exact request headers being sent.

typescript
1// Correct authentication header
2const authHeaders = {
3 "X-Api-Key": apiKey, // NOT Authorization: Bearer
4 "Content-Type": "application/json",
5};

Stopping the timer returns 404 or does not stop the running entry

Cause: The stop_timer PATCH endpoint requires the running entry to exist for the specified user. If the userId passed doesn't match the user who started the timer, or there is no running timer, the request returns 404 or 400.

Solution: Always get the current timer's user ID from the get_current_timer response before stopping. The stop_timer action uses PATCH /workspaces/{workspaceId}/user/{userId}/time-entries — the userId must match who owns the running timer. If you're building a multi-user app, store each user's Clockify user ID in Supabase and use that for timer operations.

Time entry durations display as 'PT0S' or parse to 0 hours

Cause: Clockify returns duration as PT0S for in-progress (running) time entries since the end time is not yet set. These entries should display as elapsed time calculated from the start time, not parsed from the duration field.

Solution: When rendering time entries, check if the timeInterval.end field is null or empty — that indicates an in-progress entry. For in-progress entries, calculate elapsed time as Date.now() - new Date(entry.timeInterval.start).getTime() instead of parsing the duration field.

typescript
1const getDuration = (entry) => {
2 if (!entry.timeInterval.end) {
3 // Running entry — calculate elapsed
4 return (Date.now() - new Date(entry.timeInterval.start).getTime()) / 3600000;
5 }
6 // Completed entry — parse ISO duration PT2H30M
7 const match = entry.duration?.match(/PT(?:(\d+)H)?(?:(\d+)M)?/);
8 return ((+match?.[1] || 0) + (+match?.[2] || 0) / 60);
9};

Fetching time entries returns only 50 results even for weeks with more entries

Cause: Clockify paginates time entry responses with a default page size of 50 items. Weeks with active teams can have hundreds of entries across all users.

Solution: Add page-size=200 to your list_time_entries query parameters to increase the maximum per page (Clockify supports up to 200). For teams with more than 200 entries per user per period, implement pagination by checking the response count and requesting additional pages with the page parameter.

typescript
1// In the Edge Function, set a higher page size
2q.set("page-size", "200");
3// For pagination, add:
4if (params.page) q.set("page", String(params.page));

Best practices

  • Store your Clockify workspace ID as a Cloud Secret (CLOCKIFY_WORKSPACE_ID) rather than hardcoding it in Edge Function code — this makes it easy to switch between a test and production workspace.
  • Use UTC ISO timestamps consistently for start and end times in all time entry operations — Clockify stores everything in UTC and client-side timezone handling causes date range mismatches.
  • Parse ISO 8601 duration strings (PT2H30M) into decimal hours for calculations — avoid relying on the formatted duration display strings which vary by plan and settings.
  • Implement page-size=200 in all list_time_entries calls to reduce the likelihood of hitting pagination limits for active teams, and add pagination handling for periods with very high entry counts.
  • Cache workspace user lists with a 30-minute TTL — team composition changes infrequently but checking on every render is wasteful for teams with many members.
  • For the timer UI, calculate elapsed time client-side using the entry's start timestamp and setInterval rather than polling the API — this keeps the display smooth at zero additional API cost.
  • Handle the billable flag explicitly when creating time entries — Clockify defaults may or may not set entries as billable depending on project settings, so always pass billable: true or false explicitly based on your business rules.

Alternatives

Frequently asked questions

Does Clockify have a native connector in Lovable?

No, Clockify is not one of Lovable's 17 shared connectors. Integration requires an Edge Function proxy using the Clockify REST API v1. The API key is stored in Cloud Secrets and sent as an X-Api-Key header — it is never exposed to browser code.

Is Clockify's API available on the free plan?

Yes — Clockify's REST API is available on all plans including the completely free tier. This is one of Clockify's key differentiators: there are no per-user costs and no API access fees. Advanced reporting endpoints and some bulk operations require paid tiers, but basic time entry CRUD and workspace management work on the free plan.

How do I find a Clockify user's ID for making user-scoped requests?

Call the list_users action to get all workspace members — each user object includes an 'id' field (a 24-character hex string). You can also get the current API key owner's user ID via GET /user (without workspace scoping). Store the mapping of your app's users to their Clockify user IDs in Supabase so time entry requests always use the correct user ID.

Can multiple users in my app track time against the same Clockify project?

Yes — Clockify projects are workspace resources, and multiple users can log time against the same project. Each user's time entries are owned by that user but associated with the shared project. When aggregating project hours, sum time entries across all users filtered by projectId. The list_time_entries action requires a userId, so you'll need to fetch per-user and aggregate client-side or use Clockify's summary reports API for pre-aggregated data.

How should I handle the case where a user has a timer running in the native Clockify app and then opens my Lovable app?

Call get_current_timer on component mount to check for a running entry. If one exists, display it with the elapsed time calculated from its start field. Your Stop button then stops that same entry. This synchronization ensures your Lovable interface always reflects the true timer state regardless of where it was started. The same approach works in reverse — entries started in your app appear in Clockify's native interface immediately.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.