To integrate ClickUp with V0 by Vercel, create a Next.js API route at app/api/clickup/route.ts that calls the ClickUp REST API using a personal API token stored in CLICKUP_API_TOKEN. V0 generates task list, board, and dashboard components that fetch from your API route. This keeps your ClickUp token secure server-side while displaying real project data in your custom app.
Building Custom ClickUp Dashboards with V0 by Vercel
ClickUp's built-in interface is powerful but opinionated. Many teams want a custom view of their project data — a public-facing roadmap, a client portal showing only relevant tasks, or an executive dashboard combining ClickUp data with metrics from other tools. V0 lets you generate exactly that custom interface, and ClickUp's REST API provides all the data you need.
The integration works by creating a Next.js API route that authenticates with ClickUp using a personal API token stored in Vercel's environment variables. V0 generates the front-end components — task lists, kanban boards, progress indicators, or whatever view your use case requires — that fetch data from your secure API route. The ClickUp token never touches the browser, which prevents unauthorized access to your workspace data.
ClickUp's API hierarchy mirrors its UI hierarchy: workspaces contain spaces, spaces contain folders and lists, and lists contain tasks. Understanding this structure helps you write efficient API calls that fetch exactly the data you need. For most custom dashboards, you will be working primarily with the Tasks API endpoint, filtering by list or space, and sorting by priority or due date.
Integration method
V0 generates React components for displaying ClickUp tasks and project data. A Next.js API route on Vercel proxies requests to the ClickUp REST API, keeping your personal API token server-side. The components call /api/clickup/tasks rather than ClickUp directly, eliminating credential exposure and bypassing browser CORS restrictions.
Prerequisites
- A V0 account with a Next.js project at v0.dev
- A ClickUp account at clickup.com with at least one workspace and list containing tasks
- A ClickUp personal API token from your ClickUp profile settings
- Your ClickUp List ID or Space ID for the data you want to display
- A Vercel account with your V0 project connected via GitHub
Step-by-step guide
Get Your ClickUp API Token and List ID
Get Your ClickUp API Token and List ID
ClickUp uses a personal API token for authentication. You need this token plus the ID of the list (or space) you want to query before writing any code. To get your personal API token, go to ClickUp and click your profile avatar in the bottom-left corner. Select 'My Settings' from the menu. Scroll down to the 'Apps' section and click 'Generate' next to 'API Token'. If you have already generated a token before, it will be displayed there. Copy this token — it looks like a long alphanumeric string starting with 'pk_'. Keep it confidential: anyone with this token can access your entire ClickUp workspace. To find your List ID, navigate to the ClickUp list that contains the tasks you want to display. Click the three-dot menu (ellipsis) icon next to the list name in the sidebar. Select 'Copy link'. The link will look like https://app.clickup.com/XXXXXXXX/v/li/YYYYYYYYY — the numbers after '/li/' are your List ID. Alternatively, open your ClickUp list and look at the URL in the browser — the list ID appears in the URL path. If you want to fetch tasks across an entire space rather than a single list, you can find the Space ID similarly. Right-click on a space name in the sidebar, select 'Copy link', and extract the ID from the URL. Note: ClickUp also supports OAuth2 for multi-user applications where each user connects their own ClickUp account. For single-workspace dashboards where you control the data, a personal API token is simpler and perfectly appropriate.
Pro tip: ClickUp personal API tokens do not expire unless you manually regenerate them. Treat them like passwords — do not share them or commit them to version control.
Expected result: You have your ClickUp personal API token (starts with 'pk_') and your List ID (a numeric string). Both are ready to add to Vercel environment variables.
Add ClickUp Credentials to Vercel
Add ClickUp Credentials to Vercel
Store your ClickUp API token and list configuration in Vercel's environment variables so your Next.js API route can access them securely without exposing them in your code. Go to your Vercel Dashboard, open your project, click the 'Settings' tab, and select 'Environment Variables' from the left sidebar. Add the following variables: CLICKUP_API_TOKEN: Set this to your personal API token (starts with 'pk_'). This is a secret — add it for Production, Preview, and Development environments. Never add a NEXT_PUBLIC_ prefix to this variable, which would expose it in the browser JavaScript bundle. CLICKUP_LIST_ID: Set this to your ClickUp List ID (the numeric string from your list URL). This identifies which list's tasks to fetch. If you need multiple lists, you can add CLICKUP_LIST_ID_2 etc., or make the list ID a query parameter from the frontend. Click Save for each variable. After saving, trigger a redeployment to make the variables available in your deployed serverless functions. For local development, create a .env.local file in your project root containing the same variables. The V0-generated .gitignore already excludes .env.local from Git, so your credentials stay safe. You can use 'vercel env pull .env.local' from the Vercel CLI to automatically sync development-scoped variables from your Vercel project.
1# .env.local — local development only, excluded from Git2CLICKUP_API_TOKEN=pk_XXXXXXXXXXXXXXXXXXXXXXXXXXX3CLICKUP_LIST_ID=900XXXXXXXXXPro tip: If you want to display tasks from multiple ClickUp lists or spaces, consider passing the list ID as a query parameter to your API route rather than hardcoding it in an environment variable.
Expected result: Vercel Dashboard shows CLICKUP_API_TOKEN and CLICKUP_LIST_ID saved as environment variables for all deployment environments.
Create the ClickUp API Route
Create the ClickUp API Route
Create the Next.js API route that fetches tasks from ClickUp. This file runs server-side on Vercel, can safely access your environment variables, and returns cleaned task data to your React components. Create the file app/api/clickup/tasks/route.ts. The ClickUp REST API endpoint for fetching tasks in a list is: https://api.clickup.com/api/v2/list/{listId}/task. Authentication uses the Authorization header with your API token directly as the value (no 'Bearer' prefix — this is specific to ClickUp's API design). The ClickUp tasks API supports several query parameters that are useful for dashboards: subtasks (include subtasks in results), include_closed (whether to include tasks in closed status), due_date_gt and due_date_lt for date filtering (ClickUp uses Unix timestamp in milliseconds), assignees[] to filter by specific user IDs, statuses[] to filter by status names, and page for pagination (0-indexed). ClickUp returns tasks with rich metadata including id, name, description, status (with color and type), priority (with id, priority, color, orderindex), assignees (array of user objects), due_date (Unix timestamp in milliseconds), time_estimate, tags, and custom_fields. The route should transform this into a flatter structure that is easier for React components to render. Pagination in ClickUp works via a page parameter (default page size is 100). Check the last_page boolean in the response to know if there are more pages. For most dashboard use cases, the first 100 tasks are sufficient.
Create a Next.js API route at app/api/clickup/tasks/route.ts that fetches tasks from a ClickUp list using CLICKUP_API_TOKEN and CLICKUP_LIST_ID from environment variables. Support query parameters: status (filter by status name), assignee (filter by assignee user ID), include_closed (boolean). Return tasks with id, name, description, status name and color, priority label and color, assignees array, due_date as ISO string, and tags array. Handle errors with appropriate HTTP status codes.
Paste this in V0 chat
1import { NextRequest, NextResponse } from 'next/server';23interface ClickUpStatus {4 status: string;5 color: string;6 type: string;7}89interface ClickUpPriority {10 id: string;11 priority: string;12 color: string;13 orderindex: string;14}1516interface ClickUpAssignee {17 id: number;18 username: string;19 email: string;20 profilePicture: string | null;21}2223interface ClickUpTask {24 id: string;25 name: string;26 description: string;27 status: ClickUpStatus;28 priority: ClickUpPriority | null;29 assignees: ClickUpAssignee[];30 due_date: string | null;31 tags: { name: string; tag_fg: string; tag_bg: string }[];32 url: string;33}3435interface ClickUpResponse {36 tasks: ClickUpTask[];37 last_page: boolean;38}3940export async function GET(request: NextRequest) {41 const apiToken = process.env.CLICKUP_API_TOKEN;42 const listId = process.env.CLICKUP_LIST_ID;4344 if (!apiToken || !listId) {45 return NextResponse.json(46 { error: 'ClickUp configuration missing' },47 { status: 500 }48 );49 }5051 const { searchParams } = new URL(request.url);52 const statusFilter = searchParams.get('status');53 const includeClosed = searchParams.get('include_closed') === 'true';5455 const clickupUrl = new URL(56 `https://api.clickup.com/api/v2/list/${listId}/task`57 );58 clickupUrl.searchParams.set('include_closed', String(includeClosed));59 clickupUrl.searchParams.set('subtasks', 'true');60 if (statusFilter) {61 clickupUrl.searchParams.set('statuses[]', statusFilter);62 }6364 try {65 const response = await fetch(clickupUrl.toString(), {66 headers: {67 Authorization: apiToken,68 'Content-Type': 'application/json',69 },70 next: { revalidate: 60 },71 });7273 if (!response.ok) {74 const error = await response.text();75 console.error('ClickUp API error:', error);76 return NextResponse.json(77 { error: `ClickUp returned ${response.status}` },78 { status: response.status }79 );80 }8182 const data: ClickUpResponse = await response.json();8384 const tasks = data.tasks.map((task) => ({85 id: task.id,86 name: task.name,87 description: task.description || '',88 status: {89 name: task.status.status,90 color: task.status.color,91 type: task.status.type,92 },93 priority: task.priority94 ? {95 label: task.priority.priority,96 color: task.priority.color,97 }98 : null,99 assignees: task.assignees.map((a) => ({100 id: a.id,101 username: a.username,102 email: a.email,103 avatar: a.profilePicture,104 })),105 dueDate: task.due_date106 ? new Date(parseInt(task.due_date)).toISOString()107 : null,108 tags: task.tags.map((t) => t.name),109 url: task.url,110 }));111112 return NextResponse.json({ tasks, total: tasks.length });113 } catch (error) {114 console.error('Failed to fetch ClickUp tasks:', error);115 return NextResponse.json(116 { error: 'Failed to fetch tasks' },117 { status: 500 }118 );119 }120}Pro tip: ClickUp's API does not use 'Bearer' before the token in the Authorization header, unlike most REST APIs. Sending 'Bearer pk_...' will return a 401 error.
Expected result: Calling /api/clickup/tasks returns a JSON object with a tasks array. Each task contains name, status, priority, assignees, dueDate, and tags in a clean format ready for React components.
Generate Task Dashboard Components with V0
Generate Task Dashboard Components with V0
With the API route in place, prompt V0 to generate the React components that display your ClickUp data. The most effective V0 prompts for project management interfaces specify the exact data fields available and the visual layout you want. Before prompting V0, test your API route by navigating to /api/clickup/tasks in your browser after deploying, or by running the project locally. Note the exact shape of the data returned — especially the status names and priority labels your ClickUp workspace uses. These will vary by workspace (e.g., your statuses might be 'Backlog', 'In Review', 'Complete' rather than generic names). Describe the UI layout explicitly in your V0 prompt. For a kanban board, specify the column names (matching your ClickUp status names) and how cards should look. For a list view, specify the columns and any sorting or filtering controls. For a dashboard, describe the summary metrics you want at the top (total tasks, overdue count, completion percentage) and the detailed task list below. Ask V0 to handle the async data fetching pattern — a loading skeleton state while the fetch is pending, an error state with a retry button, and an empty state for when the tasks array is empty. These three states are essential for a polished component. If you want tasks to automatically refresh without a page reload, ask V0 to add polling using setInterval inside a useEffect hook. A 30-second refresh interval is reasonable for most dashboards.
Create a task dashboard page that fetches from /api/clickup/tasks on load. Data structure: each task has id, name, description, status.name, status.color, priority (nullable, with label and color), assignees array (with username), dueDate (ISO string or null), and tags array. Display tasks in a kanban board with columns for each unique status. Each task card shows the name, priority badge colored by priority.color, assignee usernames as small chips, and due date formatted as 'Mar 31'. Overdue tasks (dueDate in the past) should have a red due date text. Show a loading skeleton and error state. Add a status filter dropdown above the board.
Paste this in V0 chat
Pro tip: Tell V0 the specific status names used in your ClickUp workspace when prompting for a kanban board. Generic status names like 'Todo' may not match your actual workflow statuses.
Expected result: V0 generates a task board or list component that fetches from /api/clickup/tasks and renders tasks with status columns, priority badges, assignee chips, and due dates — with loading and error states.
Add Task Creation from Your V0 App
Add Task Creation from Your V0 App
A read-only dashboard is useful, but many teams also want to create tasks directly from their custom app without switching to ClickUp. Add a POST handler to create new ClickUp tasks from a V0 form component. Create app/api/clickup/tasks/route.ts (or add a POST handler to the existing file). The ClickUp API endpoint for creating a task in a list is POST https://api.clickup.com/api/v2/list/{listId}/task with a JSON body. Required fields are name (task name) and optionally description, priority (1=urgent, 2=high, 3=normal, 4=low as integers), due_date (Unix timestamp in milliseconds), assignees (array of user IDs), and tags (array of tag name strings). V0 can generate a task creation modal or drawer that the user opens by clicking an 'Add Task' button. The form collects the task name (required), description (optional text area), priority (dropdown with Urgent/High/Normal/Low), due date (date picker), and optionally assignee. On form submission, the component POSTs to your /api/clickup/tasks endpoint, which creates the task in ClickUp and returns the created task object. After creating a task, the component should refetch the task list to show the new task immediately. Ask V0 to invalidate the task list cache after a successful creation so the UI stays in sync with ClickUp. For teams that use ClickUp heavily and want deep two-way sync with custom approval workflows or automated status transitions, RapidDev's team can help design a more sophisticated integration using ClickUp webhooks to push real-time updates to your Vercel app.
Add an 'Add Task' button to the dashboard header that opens a modal form. The form has fields for Task Name (required text input), Description (optional textarea), Priority (dropdown: Urgent, High, Normal, Low), and Due Date (date picker). On submit, POST to /api/clickup/tasks with the form data. Close the modal and refresh the task list on success. Show a loading spinner on the submit button during submission.
Paste this in V0 chat
1// Add POST handler to app/api/clickup/tasks/route.ts2export async function POST(request: NextRequest) {3 const apiToken = process.env.CLICKUP_API_TOKEN;4 const listId = process.env.CLICKUP_LIST_ID;56 if (!apiToken || !listId) {7 return NextResponse.json(8 { error: 'ClickUp configuration missing' },9 { status: 500 }10 );11 }1213 let body: {14 name: string;15 description?: string;16 priority?: number;17 dueDate?: string;18 };1920 try {21 body = await request.json();22 } catch {23 return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });24 }2526 if (!body.name || body.name.trim().length === 0) {27 return NextResponse.json({ error: 'Task name is required' }, { status: 400 });28 }2930 const clickupPayload: Record<string, unknown> = {31 name: body.name.trim(),32 };3334 if (body.description) clickupPayload.description = body.description;35 if (body.priority) clickupPayload.priority = body.priority;36 if (body.dueDate) {37 clickupPayload.due_date = new Date(body.dueDate).getTime();38 }3940 try {41 const response = await fetch(42 `https://api.clickup.com/api/v2/list/${listId}/task`,43 {44 method: 'POST',45 headers: {46 Authorization: apiToken,47 'Content-Type': 'application/json',48 },49 body: JSON.stringify(clickupPayload),50 }51 );5253 if (!response.ok) {54 const error = await response.text();55 console.error('ClickUp create error:', error);56 return NextResponse.json(57 { error: 'Failed to create task' },58 { status: response.status }59 );60 }6162 const task = await response.json();63 return NextResponse.json({ success: true, taskId: task.id });64 } catch (error) {65 console.error('ClickUp task creation failed:', error);66 return NextResponse.json(67 { error: 'Failed to create task' },68 { status: 500 }69 );70 }71}Pro tip: ClickUp priority values are integers, not strings: 1=urgent, 2=high, 3=normal, 4=low. Due dates are Unix timestamps in milliseconds, so multiply JavaScript Date.getTime() directly (it already returns milliseconds).
Expected result: Submitting the task creation form adds a new task to your ClickUp list. The dashboard task list refreshes to show the newly created task with the correct status, priority, and due date.
Common use cases
Client-Facing Project Status Portal
An agency wants to give clients a read-only portal showing the status of their active project tasks without sharing full ClickUp access. V0 generates a branded portal page that fetches tasks from a specific ClickUp list and displays them grouped by status column — To Do, In Progress, Done — with due dates and priority indicators.
Build a project status portal that fetches tasks from /api/clickup/tasks. Group tasks into three columns by status: 'To Do', 'In Progress', and 'Done'. Show each task with its name, due date (formatted as 'Mar 31'), priority badge (urgent=red, high=orange, normal=blue, low=grey), and an optional one-line description. Use a clean card layout with a white background and subtle border shadows.
Copy this prompt to try it in V0
Engineering Sprint Dashboard
A startup's engineering team wants a simplified sprint view showing only their current sprint tasks grouped by assignee. V0 generates a dashboard showing team member cards, each listing their assigned tasks with completion checkboxes and estimated time. Real-time status updates are visible without logging into ClickUp.
Create a sprint dashboard that loads tasks from /api/clickup/tasks. Group tasks by assignee name. For each assignee, show their avatar initials, name, and a list of their tasks with a checkbox for done status, task name, and estimated hours. Add a summary row at the top showing total tasks, completed tasks, and overall sprint completion percentage.
Copy this prompt to try it in V0
Public Product Roadmap
A SaaS company wants a public roadmap page powered by ClickUp tasks tagged as roadmap items. V0 generates a timeline-style roadmap with cards organized by quarter, showing feature names, status badges, and brief descriptions pulled from ClickUp task descriptions.
Build a product roadmap page that fetches from /api/clickup/tasks. Display tasks as roadmap cards organized in a timeline layout by due date quarter (Q1 2026, Q2 2026, etc.). Each card shows the task name as the feature title, a status badge (Planned/In Progress/Shipped), and the first 120 characters of the task description. Sort by due date ascending.
Copy this prompt to try it in V0
Troubleshooting
API route returns 401 Unauthorized
Cause: The ClickUp Authorization header format is unusual — it requires just the token value without a 'Bearer' prefix. Using 'Bearer pk_...' will cause a 401 error.
Solution: Set the Authorization header to the raw token value: Authorization: apiToken — not Authorization: Bearer ${apiToken}. Double-check that the CLICKUP_API_TOKEN environment variable is set correctly in Vercel and that you redeployed after adding it.
1// Correct ClickUp auth header — no 'Bearer' prefix2headers: {3 Authorization: process.env.CLICKUP_API_TOKEN!,4 'Content-Type': 'application/json',5}Tasks array is empty even though ClickUp list has tasks
Cause: The include_closed parameter defaults to false, so tasks in closed or done statuses are excluded. Alternatively, you may be querying the wrong List ID — spaces and folders have different ID formats than lists.
Solution: Verify the List ID is correct by checking it directly from the ClickUp list URL (look for '/li/' in the URL). Add include_closed=true to your API call to include completed tasks. Use the ClickUp API explorer at developer.clickup.com to test your list ID and token before deploying.
1// Include completed/closed tasks2clickupUrl.searchParams.set('include_closed', 'true');3// Verify you have the right endpoint for lists vs folders vs spaces4// List: /api/v2/list/{listId}/task5// Folder: /api/v2/folder/{folderId}/task6// Space: /api/v2/space/{spaceId}/taskDue dates display as wrong dates or are off by one day
Cause: ClickUp returns due dates as Unix timestamps in milliseconds as strings. If you parse them without parseInt() first, the timestamp may be treated incorrectly. Timezone differences between Vercel's UTC servers and the user's local browser can also shift displayed dates by one day.
Solution: Always use parseInt(task.due_date) before passing the value to new Date(). When displaying dates in React components, use UTC methods or a library like date-fns with timezone awareness to prevent off-by-one day errors from timezone shifts.
1// Correct due date parsing2const dueDate = task.due_date3 ? new Date(parseInt(task.due_date)).toISOString()4 : null;56// Display in component (UTC to avoid timezone shift)7const displayDate = dueDate8 ? new Date(dueDate).toLocaleDateString('en-US', {9 month: 'short',10 day: 'numeric',11 timeZone: 'UTC',12 })13 : 'No due date';Status filter parameter does not return filtered results
Cause: ClickUp status names are case-sensitive and must exactly match the status names in your workspace. The statuses[] query parameter expects the exact status string including spacing and capitalization.
Solution: Log the status names returned by your API route without filtering, then use those exact strings for filtering. ClickUp status names like 'in progress' are lowercase in the API even if they appear differently in the UI. Use the exact string from the task.status.status field in your filter.
1// Use exact status name from your workspace2// First fetch without filter to see actual status names:3// GET /api/clickup/tasks4// Then filter with exact name:5// GET /api/clickup/tasks?status=in%20progressBest practices
- Store CLICKUP_API_TOKEN without a NEXT_PUBLIC_ prefix to prevent browser exposure — ClickUp tokens grant full workspace access.
- Cache ClickUp API responses for at least 30-60 seconds using Next.js fetch caching (next: { revalidate: 60 }) to avoid hitting ClickUp's API rate limits on dashboard reloads.
- Use ClickUp's built-in pagination (page parameter) rather than fetching all tasks at once — large lists can return thousands of tasks and slow your app significantly.
- Include error boundaries in your V0 task components so a ClickUp API failure shows a user-friendly message rather than crashing the entire page.
- Create separate API routes for reading tasks (/api/clickup/tasks GET) and creating tasks (/api/clickup/tasks POST) with different caching strategies — reads can be cached, writes should not be.
- Validate that task names are not empty and that priority values are valid integers (1-4) in your POST handler before calling the ClickUp API.
- Consider using ClickUp webhooks for real-time task updates in production apps rather than polling — webhooks eliminate the polling delay and reduce API calls significantly.
Alternatives
Asana is a focused task management tool with a simpler API than ClickUp, better suited for teams that want straightforward project tracking without ClickUp's all-in-one feature set.
Monday.com has a more visual board-based interface and a GraphQL API, making it a good choice if your team prefers highly customizable workflows over ClickUp's hierarchical structure.
Trello uses a simple kanban board model with a well-documented REST API, ideal for smaller teams that find ClickUp's feature depth overwhelming.
Frequently asked questions
Does ClickUp have an official npm package I can use?
ClickUp does not have an official npm SDK as of March 2026. The recommended approach for Next.js apps is calling the ClickUp REST API directly using fetch() in your API route, as shown in this guide. There are community-maintained npm packages, but using the REST API directly gives you more control and avoids dependency on unmaintained packages.
Can I use OAuth2 instead of a personal API token for ClickUp?
Yes, ClickUp supports OAuth2 for multi-user apps where each user connects their own ClickUp account. You register your app at developer.clickup.com, implement the authorization code flow, and store per-user tokens. For single-workspace dashboards or internal tools where you own the ClickUp account, a personal API token is simpler and sufficient.
What are ClickUp's API rate limits?
ClickUp's API rate limit is 100 requests per minute per token. For dashboards that are loaded frequently, cache your API route responses using Next.js's built-in fetch caching (next: { revalidate: 60 }) to avoid hitting this limit. If multiple users are viewing the dashboard simultaneously, cached responses serve all requests from a single upstream ClickUp call.
Can I display tasks from multiple ClickUp lists in one dashboard?
Yes. The simplest approach is making the list ID a query parameter rather than a hardcoded environment variable. Your API route accepts a listId query parameter, validates it against an allowlist of permitted IDs, and fetches from the corresponding ClickUp list. This lets a single deployed app show tasks from different lists depending on which URL or filter the user selects.
How do I handle ClickUp custom fields in my API route?
ClickUp tasks have a custom_fields array in the API response, where each item has an id, name, type, and value. You can include custom_fields=true in your API request to fetch them. In your API route, transform the custom_fields array into a key-value object using the field name as the key, which makes the data much easier to use in V0 components.
Is it safe to display ClickUp task data publicly on a website?
Your API route fetches whatever tasks are in the ClickUp list you specify — including any task names, descriptions, and assignee names. Before making a page public, verify that the ClickUp list you are exposing does not contain sensitive internal information. Consider creating a dedicated ClickUp list for public-facing content and only connecting your API route to that list.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation