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

How to Integrate Lovable with Asana

Integrating Asana with Lovable requires Edge Functions to proxy the Asana REST API. Store your Asana personal access token or OAuth2 token in Cloud Secrets, create an Edge Function that navigates Asana's workspace → project → section → task → subtask hierarchy, and build custom task views in Lovable's React frontend. Asana's API supports custom fields, task dependencies, portfolios, and goals — making it ideal for flexible, workflow-driven project apps.

What you'll learn

  • How to generate an Asana personal access token and store it in Cloud Secrets
  • How to build a Deno Edge Function that navigates Asana's workspace, project, section, and task hierarchy
  • How to fetch tasks with custom field values and display them in a Lovable component
  • How to create, update, and complete tasks in Asana from a Lovable interface
  • How to use Asana's opt_fields parameter to fetch only the data your UI needs, reducing API calls
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read40 minutesProductivityMarch 2026RapidDev Engineering Team
TL;DR

Integrating Asana with Lovable requires Edge Functions to proxy the Asana REST API. Store your Asana personal access token or OAuth2 token in Cloud Secrets, create an Edge Function that navigates Asana's workspace → project → section → task → subtask hierarchy, and build custom task views in Lovable's React frontend. Asana's API supports custom fields, task dependencies, portfolios, and goals — making it ideal for flexible, workflow-driven project apps.

Why integrate Asana with Lovable?

Asana is one of the most flexible project management platforms available, trusted by teams ranging from small startups to large enterprises. Unlike opinionated tools like Basecamp, Asana's strength is customizability: custom fields let you add structured data to any task, sections organize tasks within projects, portfolios group related projects together, and goals connect day-to-day work to company objectives. This flexibility makes Asana an ideal system of record for teams with complex, evolving workflows.

Building a custom Lovable app on top of Asana data becomes valuable when Asana's own interface isn't quite right for a specific use case: client-facing portals that show filtered project data, custom intake forms where form submissions create structured Asana tasks with pre-populated custom field values, reporting dashboards that aggregate task data across multiple portfolios, or internal operations tools that automate task creation in response to business events.

Asana's REST API v1 is well-documented and uses a consistent pattern: resources are accessed at app.asana.com/api/1.0/{resource_type}/{gid}, where GID is Asana's global identifier (a large integer). The most important API pattern to understand is the opt_fields parameter — by default, Asana returns minimal data for list operations. Adding opt_fields=name,due_date,assignee,custom_fields,status.name to your request tells Asana exactly which fields to include, reducing response size and API calls significantly. This tutorial shows you how to use opt_fields effectively for common task display scenarios.

Integration method

Edge Function Integration

Asana has no native Lovable connector. All Asana API calls use the REST API v1 at app.asana.com/api/1.0, proxied through Supabase Edge Functions. Personal access tokens or OAuth2 bearer tokens are stored in Cloud Secrets and sent as Authorization headers. Edge Functions navigate Asana's task hierarchy to create, read, update, and delete tasks, sections, and projects, returning structured data to your Lovable frontend.

Prerequisites

  • A Lovable project with Cloud enabled
  • An Asana account with at least one workspace and project
  • An Asana personal access token — generate at app.asana.com/0/my-apps → Create new token → Personal access token
  • The GIDs (global identifiers) of the projects and workspaces you want to integrate with — found in Asana URLs (app.asana.com/0/PROJECT_GID/list) or via the API
  • An understanding of your Asana project structure: which sections exist, which custom fields are used, and the GIDs of any custom fields you want to set programmatically

Step-by-step guide

1

Generate your Asana personal access token and store it in Cloud Secrets

Asana personal access tokens (PATs) give server-side access to the Asana workspace on behalf of the token owner. Unlike OAuth2 tokens, PATs don't expire and don't require a redirect flow — they're ideal for server-to-server integrations where your app accesses your own Asana workspace. To generate one, log into Asana and navigate to app.asana.com/0/my-apps (or click your profile photo in the top-right corner, then select 'My Settings', then the 'Apps' tab, then 'Manage Developer Apps'). Click 'Create new token', give it a name like 'Lovable Integration', and copy it immediately — Asana shows the token only once. With your token copied, open your Lovable project and access the Cloud tab by clicking '+' at the top of the screen. Scroll to the Secrets panel and click 'Add secret'. Enter ASANA_ACCESS_TOKEN as the name and paste your token as the value. Click Save. Lovable encrypts this immediately — it will only be accessible from your Edge Functions via Deno.env.get('ASANA_ACCESS_TOKEN') and never from any client-side code. Asana PATs have full read-write access to the token owner's workspace, so treat this credential with the same care as a password.

Pro tip: To find GIDs for your projects and custom fields, you can call the Asana API directly from a browser after setting up auth: fetch https://app.asana.com/api/1.0/workspaces to list workspaces, then /projects?workspace={gid} to list projects. Or find them in Asana URLs — the large number in the URL path is the GID.

Expected result: ASANA_ACCESS_TOKEN appears in Cloud Secrets with a masked value. The secret is ready for use in Edge Functions.

2

Create the Asana proxy Edge Function with opt_fields support

The Asana REST API v1 is one of the most developer-friendly project management APIs available. All resources use consistent patterns: GET requests with optional opt_fields query parameters to control returned data, POST requests to create resources, PUT requests to update them, and DELETE for removal. The opt_fields parameter is particularly important for performance — without it, list operations return only GID and resource_type, requiring additional requests for each item. The Edge Function below handles the most common Asana operations through an action-based routing pattern. It supports fetching workspaces, projects, sections, and tasks — including tasks with custom field values. The custom fields response requires special handling since Asana returns all custom fields defined in the workspace for each task, and you need to match the field GID to find the value you care about. The function also handles task creation with custom field values, which requires a custom_fields object mapping field GIDs to their values.

Lovable Prompt

Create a Supabase Edge Function at supabase/functions/asana-proxy/index.ts. Read ASANA_ACCESS_TOKEN from Deno.env.get(). Handle CORS. Accept POST with action and params. Implement: 'list_workspaces' (GET /workspaces), 'list_projects' with params.workspaceGid (GET /projects?workspace={gid}&opt_fields=name,gid,color,status), 'list_sections' with params.projectGid (GET /projects/{gid}/sections), 'list_tasks' with params.projectGid and optional params.sectionGid and params.optFields (GET /tasks?project={gid} or /sections/{gid}/tasks with opt_fields), 'get_task' with params.taskGid (GET /tasks/{gid}?opt_fields=name,completed,due_on,notes,assignee.name,custom_fields,memberships.section.name), 'create_task' with params.projectGid, params.name, params.notes, params.dueOn, params.sectionGid, params.customFields (POST /tasks), 'update_task' with params.taskGid and update fields, 'complete_task' (PUT /tasks/{gid} with completed:true). Use Bearer token auth. Base URL https://app.asana.com/api/1.0.

Paste this in Lovable chat

supabase/functions/asana-proxy/index.ts
1// supabase/functions/asana-proxy/index.ts
2import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
3
4const BASE = "https://app.asana.com/api/1.0";
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 token = Deno.env.get("ASANA_ACCESS_TOKEN");
15 if (!token) {
16 return new Response(JSON.stringify({ error: "ASANA_ACCESS_TOKEN not set" }), {
17 status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" },
18 });
19 }
20 const auth = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
21 const { action, params = {} } = await req.json();
22 let resp;
23 switch (action) {
24 case "list_workspaces":
25 resp = await fetch(`${BASE}/workspaces`, { headers: auth });
26 break;
27 case "list_projects": {
28 const q = new URLSearchParams({ workspace: params.workspaceGid, opt_fields: "name,gid,color,current_status" });
29 resp = await fetch(`${BASE}/projects?${q}`, { headers: auth });
30 break;
31 }
32 case "list_sections":
33 resp = await fetch(`${BASE}/projects/${params.projectGid}/sections?opt_fields=name,gid`, { headers: auth });
34 break;
35 case "list_tasks": {
36 const optFields = params.optFields || "name,gid,completed,due_on,assignee.name,custom_fields";
37 const base = params.sectionGid
38 ? `${BASE}/sections/${params.sectionGid}/tasks`
39 : `${BASE}/tasks?project=${params.projectGid}`;
40 resp = await fetch(`${base}&opt_fields=${optFields}`, { headers: auth });
41 break;
42 }
43 case "get_task":
44 resp = await fetch(`${BASE}/tasks/${params.taskGid}?opt_fields=name,completed,due_on,notes,assignee.name,custom_fields,memberships.section.name,permalink_url`, { headers: auth });
45 break;
46 case "create_task":
47 resp = await fetch(`${BASE}/tasks`, {
48 method: "POST", headers: auth,
49 body: JSON.stringify({
50 data: {
51 name: params.name, notes: params.notes, due_on: params.dueOn,
52 projects: [params.projectGid],
53 ...(params.sectionGid && { memberships: [{ project: params.projectGid, section: params.sectionGid }] }),
54 ...(params.customFields && { custom_fields: params.customFields }),
55 }
56 })
57 });
58 break;
59 case "update_task":
60 resp = await fetch(`${BASE}/tasks/${params.taskGid}`, {
61 method: "PUT", headers: auth,
62 body: JSON.stringify({ data: params.updates })
63 });
64 break;
65 case "complete_task":
66 resp = await fetch(`${BASE}/tasks/${params.taskGid}`, {
67 method: "PUT", headers: auth,
68 body: JSON.stringify({ data: { completed: true } })
69 });
70 break;
71 default:
72 return new Response(JSON.stringify({ error: `Unknown action: ${action}` }), {
73 status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" },
74 });
75 }
76 const data = await resp.json();
77 return new Response(JSON.stringify(data), {
78 status: resp.status, headers: { ...corsHeaders, "Content-Type": "application/json" },
79 });
80});

Pro tip: Asana's list endpoints return a 'data' array wrapped in an envelope — your frontend code should always access response.data to get the actual array of resources, not the response object directly.

Expected result: The Edge Function deploys successfully. Calling it with action 'list_workspaces' returns your Asana workspaces. Calling 'list_projects' with a workspace GID returns your projects.

3

Display Asana tasks with custom fields in Lovable

Asana tasks can carry rich metadata through custom fields — text fields, number fields, enum (dropdown) fields, multi-enum fields, date fields, and people fields. When you fetch tasks with opt_fields=custom_fields, each task returns a custom_fields array where every entry has a gid (field identifier), name (field display name), type (text_value, number_value, enum_value, etc.), and the corresponding value field. Your frontend needs to iterate this array and render each field type appropriately. The following Lovable prompt generates a task list component that handles custom field rendering. It creates a column-configurable table where users can choose which custom fields to display, sorts tasks by due date, and provides a detail panel that shows all task information when a row is clicked. This pattern works well for teams that have invested in custom fields as their primary metadata layer in Asana.

Lovable Prompt

Create an AsanaTaskList component that accepts projectGid and optional sectionGid as props. Fetch tasks via my asana-proxy Edge Function with action 'list_tasks', requesting opt_fields including custom_fields. Display tasks in a table with columns: Task Name, Assignee, Due Date, Status (completed or open), and columns for each unique custom field found in the task data. For custom fields: render enum types as colored badges using the enum_value.name, number types as plain numbers, date types formatted as MMM DD YYYY, text types as truncated strings. Clicking a row opens a side panel with the full task details fetched via action 'get_task'. Include a Complete button in the side panel that calls action 'complete_task'.

Paste this in Lovable chat

Pro tip: Custom field GIDs are stable identifiers — if you know the GID of a specific custom field (e.g., your Priority field), you can filter the task's custom_fields array by GID for reliable access rather than searching by name (which can break if someone renames the field).

Expected result: The AsanaTaskList component shows tasks in a table with dynamic custom field columns. Clicking a task row opens a detail panel showing all task information. The Complete button marks the task done in Asana immediately.

4

Build a task creation form with custom field mapping

Creating Asana tasks from a Lovable form requires mapping form field values to Asana custom field GIDs. This is the most complex part of the Asana integration — each custom field in Asana has a unique GID, and when creating a task you must pass a custom_fields object where keys are the field GIDs and values are the field values (for enum fields, the value is the enum option GID, not the display name). The recommended approach is to fetch the project's custom field schema first — available through the project's custom_field_settings endpoint — which returns the field GIDs, names, and for enum fields, the option GIDs and names. Store this schema and use it to build your form fields dynamically. For complex multi-project integrations where different projects have different custom field schemas, RapidDev's team can help design a configuration layer that maps your form fields to the right Asana custom field GIDs across projects.

Lovable Prompt

Add a 'New Task' button above the AsanaTaskList that opens a modal form. First fetch the project's custom fields by calling GET /projects/{projectGid}?opt_fields=custom_field_settings.custom_field.gid,custom_field_settings.custom_field.name,custom_field_settings.custom_field.type,custom_field_settings.custom_field.enum_options from my asana-proxy Edge Function. Dynamically render form inputs for each custom field: text inputs for text type, number inputs for number type, dropdowns with enum option names for enum type. Include required fields: Task Name, Due Date, Assignee (fetch from project members), and Section (fetch from project sections). On submit, call action 'create_task' with a custom_fields object mapping field GIDs to values — for enum fields, map the selected display name back to the enum option GID before submitting.

Paste this in Lovable chat

Pro tip: For enum custom fields, the task creation API requires the enum option's GID (e.g., '12345678') as the value — NOT the display name ('High Priority'). Always map back from display name to GID using the enum_options array before calling create_task.

Expected result: The New Task modal dynamically renders form fields matching the project's custom field schema. Submitting creates the task in Asana with all custom fields populated. The new task immediately appears in the task list.

5

Add Asana webhook support for real-time updates

For apps that need to stay synchronized with Asana in real time — rather than polling on a fixed interval — Asana supports webhooks. You create a webhook subscription via POST to /webhooks, specifying the resource GID (project GID to watch), the target URL (your Edge Function), and the filters (which event types to receive). Asana then POSTs an X-Hook-Secret handshake request to your URL — your Edge Function must echo back this secret in the X-Hook-Secret response header to complete registration. After registration, Asana sends webhook events with a signature in the X-Hook-Signature header. Unlike some platforms, Asana webhook signatures use HMAC-SHA256 and require you to store the X-Hook-Secret from the handshake. Store this value in Cloud Secrets as ASANA_WEBHOOK_SECRET after the initial handshake — your ongoing webhook handler verifies all subsequent events against this secret.

Lovable Prompt

Create a Supabase Edge Function at supabase/functions/asana-webhook/index.ts. Handle two scenarios: (1) Initial handshake: if the request has an X-Hook-Secret header, store it (log it for now) and return a 200 response with that value as the X-Hook-Secret response header. (2) Event delivery: verify the HMAC-SHA256 signature from X-Hook-Signature against the request body using the stored ASANA_WEBHOOK_SECRET from Deno.env.get(). If valid, parse the events array and for each event of type ADDED or CHANGED on resource_type task, log the task GID and change type. Return 200 for all valid requests.

Paste this in Lovable chat

Pro tip: Asana webhook handshakes can only complete when your app is deployed — not in Lovable's preview mode. Deploy your app first, then register the webhook using the deployed Edge Function URL from Cloud → Logs.

Expected result: The webhook Edge Function is deployed and successfully completes the Asana handshake when registered. Subsequent task changes in Asana trigger events visible in Cloud → Logs within seconds.

Common use cases

Custom task dashboard with Asana custom field values

A product team tracks features in Asana using custom fields for priority score, effort estimate, and target quarter. The default Asana interface shows these but doesn't let the team create custom visualizations. The Edge Function fetches tasks with their custom field values using opt_fields, and the Lovable frontend renders a prioritization matrix that plots tasks by effort vs priority score for quarterly planning sessions.

Lovable Prompt

Build a prioritization dashboard at /roadmap that fetches tasks from Asana project GID 1234567890. Use opt_fields to include name, custom_fields, due_on, assignee.name, and completed. From the custom fields, read Priority Score (a number field) and Effort (enum: Small/Medium/Large). Render tasks as a 2x2 matrix: X axis is Effort (Small to Large), Y axis is Priority Score (low to high). Color code cards by the Effort enum value. Click a card to see full task details. Add filter checkboxes for Quarter custom field values.

Copy this prompt to try it in Lovable

Client intake form that creates structured Asana tasks

A consulting firm receives client requests and wants a branded intake form that creates properly structured Asana tasks with the correct custom field values pre-filled. When the client submits the form, the Edge Function creates a new task in the correct project and section, sets all custom field values from the form data, assigns it to the right team member based on service type, and returns the Asana task URL for confirmation.

Lovable Prompt

Create a client request form at /submit-request with fields: Client Name, Service Type (dropdown: Strategy, Design, Development, Analytics), Project Description, Budget Range, and Target Start Date. On submit, call my Asana Edge Function to create a task in project GID 9876543210, section GID 1111111111. Set the task name to 'New Request: Client Name - Service Type'. Set custom fields: service_type_gid to the selected service, budget_range_gid to the selected budget, target_date_gid to the start date. Add a follower with team lead GID 2222222222. Return the task permalink_url and show it in a success message.

Copy this prompt to try it in Lovable

Portfolio health report across multiple Asana projects

An operations manager oversees six projects grouped in an Asana portfolio and wants a weekly health report showing task completion rates, overdue item counts, and upcoming milestones across all projects. The Edge Function fetches the portfolio's project list, then makes parallel requests for each project's task statistics, aggregating them into a health score and summary payload for the report frontend.

Lovable Prompt

Build a portfolio health page at /portfolio-health that reads portfolio GID 5555555555. For each project in the portfolio, count: total tasks, completed tasks this week, tasks past due date, and tasks due in the next 7 days. Display projects in a card grid with a health indicator (green if >80% complete, yellow if 50-80%, red if <50%). Show a portfolio-level summary: total open tasks, completion percentage this sprint, and a list of the 10 most overdue tasks sorted by days overdue across all projects.

Copy this prompt to try it in Lovable

Troubleshooting

API returns empty data arrays even though the project has tasks

Cause: Asana's list endpoints return only GID and resource_type by default when opt_fields is not specified. If opt_fields is missing or incorrect, the tasks array will appear nearly empty with only GIDs visible.

Solution: Always include an opt_fields parameter when fetching task lists. Minimum useful opt_fields for task display: 'name,gid,completed,due_on,assignee.name'. Add 'custom_fields' to include custom field data. Check the request URL in Cloud → Logs to confirm the opt_fields parameter is being appended correctly.

typescript
1// Include opt_fields for useful task data
2const url = `${BASE}/tasks?project=${projectGid}&opt_fields=name,gid,completed,due_on,assignee.name,custom_fields`;

Custom field values are null or missing even when the task has them set in Asana

Cause: Custom fields must be explicitly requested via opt_fields — they are not included in default responses. Additionally, the task must have been created or updated after the custom field was added to the project.

Solution: Add 'custom_fields' to your opt_fields parameter. If the value is still null, verify in Asana's UI that the field actually has a value for that task. For enum fields specifically, the value appears under custom_field.enum_value (an object) rather than a plain string — access custom_field.enum_value.name for the display label.

typescript
1// Access enum field value correctly
2const priorityField = task.custom_fields.find(f => f.gid === PRIORITY_FIELD_GID);
3const priorityLabel = priorityField?.enum_value?.name ?? 'None';

Task creation succeeds (201 response) but the task appears in the wrong section or project

Cause: When creating a task, specifying both 'projects' and 'memberships' is required to place it in a specific section. Using only 'projects' adds the task to the project's default section (usually the first one).

Solution: To create a task in a specific section, include a 'memberships' array in the POST body with an object containing both the project GID and section GID. This overrides the default section placement. If you only need to add to a project without section control, the 'projects' array alone is sufficient.

typescript
1body: JSON.stringify({
2 data: {
3 name: taskName,
4 projects: [projectGid],
5 memberships: [{ project: projectGid, section: sectionGid }]
6 }
7})

Webhook registration fails with 'Invalid target URL' or the handshake never completes

Cause: Either the Edge Function URL is incorrect, the app is not deployed (only preview URLs work for development but Asana cannot reach them), or the handshake handler is not returning the X-Hook-Secret header correctly.

Solution: Confirm your app is deployed and the Edge Function URL is the deployed URL, not the preview URL. Get the correct URL from Cloud → Logs after deployment. Verify your webhook handler checks for the X-Hook-Secret request header and returns it as a response header (not in the body). Test by sending a request with an X-Hook-Secret header manually and confirming the response includes it.

Best practices

  • Always use opt_fields to specify exactly which fields you need — Asana list endpoints return minimal data by default, and fetching all fields for large task lists is slow and wastes API quota.
  • Cache the project's custom field schema (field GIDs, names, and enum options) in Supabase on first load rather than fetching it on every page render — the schema rarely changes.
  • Use Asana's workspace GIDs as configuration constants rather than hardcoding them in Edge Function code, allowing easy workspace switching in different environments.
  • Handle Asana's pagination (responses include a next_page object with an offset) for projects with many tasks — the default page size is 20 items per request.
  • Store custom field GIDs that your integration depends on in your Supabase configuration table, not in frontend code, so they can be updated when someone restructures the Asana project.
  • Use Asana's task stories endpoint (/tasks/{gid}/stories) to fetch comments and activity history when building detail views — stories provide the audit trail for task changes.
  • For multi-user integrations, implement OAuth2 so each user accesses only their own Asana data — a shared PAT gives all app users access as the token owner, which may not match your permission requirements.

Alternatives

Frequently asked questions

Does Asana have a native connector in Lovable?

No, Asana is not one of Lovable's 17 shared connectors. However, the Asana REST API is well-documented and works well through Edge Functions. Store your personal access token in Cloud Secrets and create an Edge Function proxy — the approach shown in this tutorial gives you full read-write access to tasks, projects, custom fields, and more.

What is the difference between an Asana personal access token and OAuth2?

A personal access token (PAT) authenticates as you and gives access to your own workspaces — it's the fastest way to get started and doesn't expire. OAuth2 is for apps where different users need to connect their own Asana accounts. If you're building an internal tool that only your team accesses, use a PAT. If you're building a product where external users connect their Asana accounts, implement OAuth2.

How do I find the GIDs I need for projects and custom fields?

Project GIDs appear in Asana URLs: app.asana.com/0/PROJECT_GID/list. Custom field GIDs require an API call — fetch GET https://app.asana.com/api/1.0/projects/{projectGid}?opt_fields=custom_field_settings.custom_field.gid,custom_field_settings.custom_field.name from your Edge Function. The response includes each custom field with its GID and name. For enum options, add custom_field_settings.custom_field.enum_options.gid,enum_options.name to the opt_fields.

Can I read tasks from multiple Asana workspaces in the same app?

Yes — the Asana API supports multiple workspaces under the same account. Your PAT has access to all workspaces the token owner belongs to. Fetch the workspace list first with GET /workspaces, then scope project and task requests to the specific workspace GID. If different users need access to different workspaces, implement OAuth2 so each user authenticates with their own credentials.

How do I handle Asana tasks with subtasks?

Subtasks in Asana are themselves task resources — they have their own GIDs and can be fetched via GET /tasks/{parentTaskGid}/subtasks. Subtasks do not appear in project task lists by default; they only appear under their parent task. To display a task hierarchy, first fetch the project's tasks, then for tasks with subtasks (indicated by the num_subtasks field being greater than 0), make an additional request to fetch the subtask list. Include opt_fields=num_subtasks in your initial task fetch to know which tasks have subtasks without fetching all of them.

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.