Skip to main content
RapidDev - Software Development Agency
bubble-integrationsBubble API Connector

Asana

Connect your Bubble app to Asana using the API Connector with a Bearer personal access token. Every Asana object uses an opaque GID identifier, so you must chain workspace → project → task calls. Without the opt_fields URL parameter, Asana silently returns only the GID and name for every object — all other fields come back null with no error message.

What you'll learn

  • How to generate an Asana personal access token and configure a Private Bearer header in Bubble
  • Why Asana uses GIDs for all objects and how to chain workspace → project → task calls in Bubble
  • Why opt_fields is critical — and what happens when you forget it (silent null fields)
  • How to wrap POST request bodies in Asana's required data key
  • How to build a work request intake form in Bubble that creates Asana tasks without requiring users to have Asana seats
  • How to handle Asana's custom fields in task responses within Bubble's API response mapper
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate16 min read2–3 hoursProductivityLast updated July 2026RapidDev Engineering Team
TL;DR

Connect your Bubble app to Asana using the API Connector with a Bearer personal access token. Every Asana object uses an opaque GID identifier, so you must chain workspace → project → task calls. Without the opt_fields URL parameter, Asana silently returns only the GID and name for every object — all other fields come back null with no error message.

Quick facts about this guide
FactValue
ToolAsana
CategoryProductivity
MethodBubble API Connector
DifficultyIntermediate
Time required2–3 hours
Last updatedJuly 2026

Turn your Bubble app into an Asana-powered work request portal

The most impactful Asana integration for Bubble founders is a work request intake portal: team members, clients, or external partners submit structured requests in your Bubble app, which automatically creates Asana tasks in the right project with custom fields pre-filled — no Asana seat required for submitters. Asana's API is well-designed but has two non-obvious behaviors that cause almost every integration to fail silently before they're discovered. First, every Asana object is identified by a GID — a long opaque string like '1234567890123456', not a human-readable name. You must fetch the workspace GID, then the project GID, then pass these to task queries. Second, Asana's default API response returns only GID and name for every object to minimize payload size. Any field you want to use — due_on, assignee, custom_fields, notes — must be explicitly listed in the opt_fields URL parameter. If you omit it, due dates and assignees come back as null with no error message, and the integration appears to be working while returning incomplete data. Get both of these right and the integration is powerful and reliable.

Integration method

Bubble API Connector

Calls Asana REST API v1 server-side using Bubble's API Connector plugin with a Private Bearer personal access token.

Prerequisites

  • A Bubble account on any plan (for read-only Asana data display); paid Bubble plan for Backend Workflows if you need scheduled sync or webhook reception
  • An Asana account — Free (up to 15 members with basic features) or any paid plan for advanced features like custom fields on Business plan
  • An Asana personal access token generated from asana.com (Profile → Apps → Developer Apps)
  • Your Asana workspace GID — you'll retrieve this in Step 3 via the API
  • The 'API Connector' plugin by Bubble installed in your app (Plugins tab → Add plugins → search 'API Connector')

Step-by-step guide

1

Generate your Asana personal access token

Log in to your Asana account at asana.com. Click your profile picture in the top-right corner and select 'My Profile Settings' (or go directly to app.asana.com/0/my-apps). Navigate to the 'Apps' tab, then find the 'Developer Apps' section and click 'Manage Developer Apps'. Click 'New Access Token', give it a descriptive name (for example, 'Bubble Integration — Production'), read and accept the API terms, then click 'Create Token'. Copy the token immediately — Asana displays it only once and there is no way to retrieve it again. If you lose it, you'll need to delete the old token and generate a new one. Asana personal access tokens do not expire automatically but can be revoked at any time from this same page. For production Bubble apps used by multiple team members, best practice is to create a dedicated service account in Asana, add it to the relevant projects, and generate the token from that account — this avoids the integration breaking if the original developer leaves the team.

Pro tip: Copy the token to a password manager immediately after generating it. Asana does not show the token value again after you close the dialog — you'll need to revoke and regenerate if you lose it.

Expected result: You have a long alphanumeric Asana personal access token ready to paste into Bubble. You know which Asana account generated it and which workspaces and projects it can access.

2

Install the API Connector and configure Asana Bearer authentication

In your Bubble editor, click the Plugins tab in the left sidebar. If you don't have the 'API Connector' plugin by Bubble installed, click 'Add plugins', search for 'API Connector', and install it — it's free. After installation, click on the API Connector in the Plugins tab. Click 'Add another API' and name it 'Asana'. Set the base URL to `https://app.asana.com/api/1.0`. In the Shared headers section, add a new header with key `Authorization` and value `Bearer <your_personal_access_token>` — replace the placeholder with your actual token. Check the 'Private' checkbox on this header. This tells Bubble to keep the token on its servers and never expose it to the browser. Add a second shared header: key = `asana-enable`, value = `new_user_task_lists,new_project_templates`. This header opts your API calls into Asana's current API behaviors and prevents deprecation warning banners from appearing in responses — without it, Asana may include warnings in response headers about deprecated API patterns.

asana-connector-config.json
1{
2 "api_name": "Asana",
3 "base_url": "https://app.asana.com/api/1.0",
4 "shared_headers": [
5 {
6 "key": "Authorization",
7 "value": "Bearer <your_personal_access_token>",
8 "private": true
9 },
10 {
11 "key": "asana-enable",
12 "value": "new_user_task_lists,new_project_templates",
13 "private": false
14 }
15 ]
16}

Pro tip: The asana-enable header is not strictly required but prevents confusing deprecation notices in API responses. Add it now so your integration is forward-compatible with Asana's API versioning.

Expected result: The 'Asana' API entry appears in your API Connector with the Bearer token header marked Private and the asana-enable header configured.

3

Fetch your workspace GID and store it as an App Constant

In the Asana API entry, click 'Add a call'. Name it 'Get Workspaces'. Set the method to GET and the path to `/workspaces`. Set 'Use as' to 'Data'. Click 'Initialize call'. Bubble sends a GET request to `https://app.asana.com/api/1.0/workspaces` and returns a JSON response with the structure `{"data": [{"gid": "123456789", "name": "Your Workspace"}]}`. If the Initialize succeeds, Bubble maps the `data` array. Note the GID value shown in the preview — this is your workspace's unique identifier in Asana. Copy this GID. Now in Bubble, go to the Data tab → App Data → App Constants (or create one via the Workflows tab). Create a new App Constant named 'Asana Workspace GID' of type text, and paste in your workspace GID. This constant becomes the foundation for all downstream Asana calls: project lists, task lists, and task creation all require the workspace GID. Using an App Constant means you set it once and reference it everywhere rather than hardcoding the GID value into each workflow individually.

asana-workspaces-response.json
1{
2 "data": [
3 {
4 "gid": "123456789012345",
5 "name": "Your Company Workspace",
6 "resource_type": "workspace"
7 }
8 ]
9}

Pro tip: Most Asana accounts have a single workspace. If the response shows multiple workspaces, identify yours by the name field and use that workspace's GID in your App Constant.

Expected result: Your Asana workspace GID is stored as a Bubble App Constant. The Initialize call succeeded and Bubble mapped the data array from the workspaces response.

4

Add the projects call and build a project selector with opt_fields

Add a new call to your Asana API entry named 'Get Projects'. Set the method to GET and the path to `/projects`. Add URL parameters: `workspace` = your workspace GID App Constant (mark as dynamic), and `opt_fields` = `gid,name,status,due_on,notes` (this is critical — without opt_fields, Asana returns only gid and name, silently omitting status and due_on). Set 'Use as' to 'Data'. Run Initialize. The response structure is `{"data": [...projects...]}` — all Asana responses wrap their payload in a `data` key. In Bubble's Initialize mapper, access projects as `result's data`. On your Bubble page, add a Dropdown element for project selection. Set its choices to the result of your 'Get Projects' call's `data` array. Set the display field to `name`. Store the selected project's `gid` in a page-level Custom State — you'll pass this GID to all downstream task calls. The GID is what Asana uses to identify the project in every subsequent request; the name is for display only.

asana-projects-request.txt
1GET https://app.asana.com/api/1.0/projects?workspace=123456789012345&opt_fields=gid,name,status,due_on,notes
2Headers:
3 Authorization: Bearer <private>
4 asana-enable: new_user_task_lists,new_project_templates

Pro tip: Always design your opt_fields list before running Initialize — Bubble maps fields based on what it receives in the Initialize response. If you add new opt_fields later, re-run Initialize to update the field mapping.

Expected result: Initialize succeeds. Bubble maps gid, name, status, due_on, and notes for each project. Your Dropdown shows Asana project names and stores the selected project's GID in a Custom State.

5

Add the tasks call with opt_fields and bind to a Repeating Group

Add a new call named 'Get Tasks'. Set the method to GET and the path to `/tasks`. Add URL parameters: `project` = the selected project GID from your Custom State (mark as dynamic), `completed_since` = `now` reversed — actually use `completed` = `false` to get only incomplete tasks — add `completed=false` as a parameter. Add the critical `opt_fields` parameter: `gid,name,due_on,assignee.name,assignee.gid,notes,custom_fields,completed,resource_type`. Each field you want to display must be listed here. The `assignee.name` and `assignee.gid` dot-notation syntax tells Asana to return the nested assignee name, not just the assignee GID. Custom fields require `custom_fields` in opt_fields — Asana then returns an array of custom field objects for each task. Set 'Use as' to 'Data'. Run Initialize with a real project GID that has tasks in it. After Initialize, add a Repeating Group to your page, set its Type of content to the data type from the tasks call, and set its data source to `Get data from an external API → Asana → Get Tasks`, passing the project GID Custom State as the dynamic parameter. Inside the Repeating Group, add Text elements for `Current cell's name`, `Current cell's due_on`, and `Current cell's assignee's name`. If `assignee` shows as blank, verify `assignee.name` is in your opt_fields list.

asana-tasks-request.txt
1GET https://app.asana.com/api/1.0/tasks?project=<project_gid>&completed=false&opt_fields=gid,name,due_on,assignee.name,assignee.gid,notes,custom_fields,completed
2Headers:
3 Authorization: Bearer <private>

Pro tip: If your Repeating Group shows tasks but assignee names are all blank, `assignee.name` is not in your opt_fields. Edit the call, add `assignee.name` to opt_fields, then re-initialize. Asana never shows an error for missing opt_fields — the field just comes back null.

Expected result: Your Repeating Group shows tasks from the selected Asana project with names, due dates, and assignee names populated. Changing the project dropdown selection refreshes the task list to the new project's tasks.

6

Add a POST /tasks call for intake form task creation

Add a new call named 'Create Task'. Set the method to POST and the path to `/tasks`. Set 'Use as' to 'Action'. Set the body type to 'JSON'. Asana requires all POST request bodies to be wrapped in a `data` key — this is mandatory and a 400 error with a generic message is returned if the wrapper is missing. Set the body to: `{"data": {"workspace": "<workspace_gid>", "projects": ["<project_gid>"], "name": "<task_name>", "notes": "<task_notes>", "assignee": "<user_gid>", "due_on": "<YYYY-MM-DD>"}}`. Mark `<workspace_gid>`, `<project_gid>`, `<task_name>`, `<task_notes>`, `<user_gid>`, and `<YYYY-MM-DD>` as dynamic parameters. The due_on date format is ISO 8601 YYYY-MM-DD with hyphens — for example, `2026-07-15`. Run Initialize with a real body (valid workspace GID, project GID, and a test task name) to map the response. Asana returns the created task object in the same `data` wrapper. On success, Bubble workflows can read back the new task's GID and name. Add a workflow in Bubble triggered by your intake form's 'Submit' button: run the 'Create Task' action with form input fields mapped to the dynamic parameters. On success, show a confirmation message or redirect the user. To populate your user GID selector, add a separate call `GET /users?workspace=<workspace_gid>&opt_fields=gid,name` and use it to populate an Assignee dropdown on your intake form.

asana-create-task-request.json
1POST https://app.asana.com/api/1.0/tasks
2Headers:
3 Authorization: Bearer <private>
4 Content-Type: application/json
5
6{
7 "data": {
8 "workspace": "<workspace_gid>",
9 "projects": ["<project_gid>"],
10 "name": "<task_name>",
11 "notes": "<task_notes>",
12 "assignee": "<user_gid>",
13 "due_on": "<YYYY-MM-DD>"
14 }
15}

Pro tip: The `projects` field in the POST body is an array even if you're adding the task to only one project — `["<project_gid>"]`. Sending it as a plain string causes a 400 error.

Expected result: Submitting your Bubble intake form creates a new task in Asana with the correct name, notes, assignee, and due date. The task appears in Asana immediately after the workflow runs.

Common use cases

Work request intake portal without Asana seats

Build a Bubble form for clients or external stakeholders to submit work requests. Each submission automatically creates an Asana task in the correct project with the right custom fields populated — eliminating manual intake and the need for submitters to have Asana access.

Bubble Prompt

Build a Bubble form page where external stakeholders can submit a work request with a title, description, priority (dropdown: Low/Medium/High), and desired deadline. On submit, create an Asana task in our 'Incoming Requests' project with the title and description mapped to the task name and notes, the priority mapped to the matching Asana custom field, and the deadline set as the task due_on date.

Copy this prompt to try it in Bubble

Cross-project task dashboard for team leads

Create an internal Bubble dashboard that aggregates tasks across multiple Asana projects, filtered by assignee and due date, so team leads can see workload distribution without switching between Asana project views.

Bubble Prompt

Create a Bubble page that shows all incomplete Asana tasks assigned to team members, grouped by assignee. Pull tasks from three specific project GIDs using opt_fields to retrieve assignee name, due_on, and task name. Highlight tasks past their due date in red.

Copy this prompt to try it in Bubble

Task completion tracker for client reporting

Build a client-facing Bubble page that shows completed tasks in their Asana project for the current month, providing a transparent delivery record without giving clients direct Asana access.

Bubble Prompt

Build a Bubble page that fetches all completed tasks in a client's Asana project from the current month. Display each completed task's name, completion date, and the name of the assignee who completed it. Use opt_fields to include completed_at, name, and assignee.name.

Copy this prompt to try it in Bubble

Troubleshooting

Task due_on, assignee, and other fields are all blank in Bubble even though Initialize succeeded

Cause: Asana's API returns only gid and name by default for all object types. Any other field — due_on, assignee, notes, custom_fields — must be explicitly listed in the opt_fields URL parameter. Without opt_fields, all other fields are silently omitted with no error.

Solution: Edit your API Connector call, add the opt_fields URL parameter with a comma-separated list of every field you need: gid,name,due_on,assignee.name,completed,notes. Re-run Initialize after adding opt_fields so Bubble picks up the new fields in its response mapper. Every time you add a new opt_field, re-initialize the call.

typescript
1GET /tasks?project=<gid>&opt_fields=gid,name,due_on,assignee.name,notes,custom_fields

Create Task call returns 400 Bad Request

Cause: Asana requires POST request bodies to be wrapped in a `data` key: `{"data": {"name": "...", ...}}`. A body without the wrapper causes a 400, but the error message is generic and doesn't explicitly say the wrapper is missing.

Solution: Check your POST /tasks body in the API Connector and ensure the outermost key is `data`. The structure must be `{"data": {"name": "Task title", "workspace": "gid", "projects": ["project_gid"]}}`. Do not send the fields directly at the root level.

typescript
1{
2 "data": {
3 "name": "Task title",
4 "workspace": "<workspace_gid>",
5 "projects": ["<project_gid>"]
6 }
7}

API Connector Initialize fails with 'There was an issue setting up your call'

Cause: The Initialize step requires a real successful API response. This typically means the personal access token is incorrect, or for parameterized calls like Get Tasks, the dynamic parameter value used during Initialize is not a valid Asana GID.

Solution: For the Get Workspaces call, ensure your Bearer token is correctly pasted (no extra spaces, no 'Bearer' prefix in the token itself — Bubble adds 'Bearer' as part of the header value). For task calls, run Initialize with a real project GID copied from your Asana URL or from the Get Projects call result.

Repeating Group shows no tasks even though the Get Tasks call initializes correctly

Cause: Asana wraps all response arrays in a `data` key: `{"data": [...tasks...]}`. If the Repeating Group is bound to the full API result instead of the `data` key, Bubble finds no list to display.

Solution: Set the Repeating Group data source to `[API result]'s data`. The top-level result is an object with a data key containing the array — bind the Repeating Group to the array inside data, not to the root result object.

Get Tasks call returns all tasks from the workspace instead of tasks from the selected project

Cause: The `project` URL parameter is missing or the dynamic parameter isn't being passed from the Custom State correctly. Without a project filter, Asana returns tasks across the workspace.

Solution: In the Repeating Group data source configuration, verify the project GID Custom State is selected as the value for the `project` dynamic parameter. Test by running the workflow manually and checking the Bubble Logs tab to confirm what project GID value is being sent.

typescript
1GET /tasks?project=<project_gid_from_state>&completed=false&opt_fields=gid,name,due_on

Backend Workflow not available for scheduled task sync

Cause: Backend Workflows (API Workflows) are only available on paid Bubble plans. Free plan apps cannot run scheduled server-side workflows.

Solution: Upgrade to a Bubble paid plan (Starter or higher) to access Backend Workflows. On a free plan, Asana data can only be fetched on user-triggered actions (page load, button click) — not on a schedule or in response to Asana webhooks.

Best practices

  • Always mark the Asana Bearer token header as Private in the API Connector — this is the only configuration needed to keep the token server-side. Never store your PAT in a Bubble database field, page input, or URL parameter.
  • Always include opt_fields in every Asana API call, listing exactly the fields you plan to display. Asana's default response returns only gid and name — omitting opt_fields silently results in null values for all other fields with no error message to indicate the problem.
  • Use a dedicated Asana service account (not a personal account) to generate the API token for production Bubble apps. This ensures the integration doesn't break if the developer who created the token leaves the team or revokes their credentials.
  • Store your workspace GID as a Bubble App Constant and your most frequently accessed project GIDs as App Constants too. Using App Constants avoids hardcoding GIDs in workflow steps and makes updates easier if project structures change.
  • Set Privacy rules on any Bubble Data Type that stores Asana task data, project GIDs, or user associations. Bubble's default setting allows all users to see all database records — scope data to the current user to prevent unauthorized data access in multi-user apps.
  • For intake forms where any user can submit tasks, add a Bubble Condition to verify the form is being submitted from an authenticated user before calling the Create Task API. This prevents anonymous form submissions from creating noise in your Asana workspace.
  • Re-run Initialize on your API Connector calls whenever you add new fields to opt_fields. Bubble's field mapping is based on the Initialize response snapshot — adding a field to opt_fields without re-initializing means Bubble doesn't know the field exists and it won't appear in the expression builder.
  • Asana custom fields are returned as an array of objects — each with a name, type, and type-specific value key (text_value, number_value, enum_value.name). When displaying custom fields in Bubble, filter the custom_fields array by name inside a Repeating Group to find the specific field you want, rather than relying on array index position which can change.

Alternatives

Frequently asked questions

Why do I need to call the workspaces endpoint before I can fetch tasks?

Asana uses opaque GID strings to identify every object — workspaces, projects, tasks, users, and custom fields all have GIDs instead of human-readable IDs. Almost every Asana API call requires a GID to scope the request. The chain is: first call GET /workspaces to get your workspace GID, then call GET /projects with that workspace GID to get project GIDs, then call GET /tasks with a project GID to get tasks. Store the workspace GID as a Bubble App Constant so you only need to look it up once.

What is opt_fields and why does Asana return null for most fields without it?

Asana defaults to returning only the GID and name for every object in API responses. This minimizes payload size for high-traffic API clients. Any additional field — due_on, assignee, notes, completed_at, custom_fields — must be explicitly requested using the opt_fields URL parameter with a comma-separated list of field names. If a field isn't in opt_fields, Asana returns null for that field without any warning or error. Always list every field you need in opt_fields before running Initialize in Bubble.

Can free Bubble plan apps use this Asana integration?

Yes, for read-only use cases and task creation triggered by user actions. Fetching project lists, displaying tasks in a Repeating Group, and submitting intake forms that create Asana tasks all work on Bubble's free plan. What requires a paid Bubble plan is Backend Workflows — needed for receiving Asana event webhooks or running scheduled syncs that pull new Asana tasks into your Bubble database automatically.

How do I display Asana custom fields in Bubble?

Add `custom_fields` to your opt_fields parameter. Asana returns custom fields as an array of objects on each task, where each object has a `name` (the field label), `type` (text, number, enum, date), and a type-specific value key — `text_value`, `number_value`, `enum_value.name`, or `date_value`. In Bubble, inside a task Repeating Group, add a nested Repeating Group bound to `Current cell's custom_fields`. Filter this inner Repeating Group by name to display a specific custom field, or display all of them. Use `Current cell's text_value` or `Current cell's enum_value's name` depending on the field type.

Does the Asana personal access token expire?

No, Asana personal access tokens do not expire automatically. However, they can be revoked at any time from asana.com → Profile → Apps → Developer Apps. If your integration suddenly stops working with 401 errors, the most common cause is the token having been revoked — either deliberately or because the Asana account that generated it was deactivated. Generate the token from a dedicated service account rather than an individual developer's account to avoid this.

We're building a complex multi-team Asana integration with custom fields and portfolio views. Where can we get help?

If your Bubble app needs to sync Asana portfolio data, handle complex custom field logic, or build real-time task dashboards with webhook updates, RapidDev's team has built production Bubble apps with project management tool integrations at this level of complexity. Book a free scoping call at rapidevelopers.com/contact.

RapidDev

Talk to an Expert

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

Book a free consultation

Integrations are where projects stall

We wire Bubble integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

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.