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.
| Fact | Value |
|---|---|
| Tool | Asana |
| Category | Productivity |
| Method | Bubble API Connector |
| Difficulty | Intermediate |
| Time required | 2–3 hours |
| Last updated | July 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
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
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.
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.
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": true9 },10 {11 "key": "asana-enable",12 "value": "new_user_task_lists,new_project_templates",13 "private": false14 }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.
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.
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.
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.
1GET https://app.asana.com/api/1.0/projects?workspace=123456789012345&opt_fields=gid,name,status,due_on,notes2Headers:3 Authorization: Bearer <private>4 asana-enable: new_user_task_lists,new_project_templatesPro 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.
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.
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,completed2Headers: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.
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.
1POST https://app.asana.com/api/1.0/tasks2Headers:3 Authorization: Bearer <private>4 Content-Type: application/json56{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.
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.
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.
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.
1GET /tasks?project=<gid>&opt_fields=gid,name,due_on,assignee.name,notes,custom_fieldsCreate 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.
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.
1GET /tasks?project=<project_gid_from_state>&completed=false&opt_fields=gid,name,due_onBackend 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
Teamwork uses HTTP Basic Auth with an API token and requires a .json suffix on every endpoint, while Asana uses a clean Bearer token with no path suffix quirks. Teamwork is purpose-built for agency client billing with time tracking and invoicing; Asana is better for general work management and portfolio tracking. Choose Teamwork if your primary use case is client billing and time reporting; choose Asana if your team is already on Asana and you're building intake forms or cross-project dashboards.
Jira uses Basic Auth with an API token and email address, and is optimized for software development workflows (sprints, bugs, story points, releases). Asana is better for non-engineering teams managing general work across departments. Both integrate with Bubble via the API Connector using similar patterns, but Jira's data model is more complex with more required fields for ticket creation.
Monday.com uses a GraphQL API with a Bearer token, which requires different query syntax than Asana's REST endpoints. Monday.com's board and item structure maps well to Bubble's Data Types for building status dashboards. Asana has a more mature API with better documentation for complex use cases like custom field management and portfolio queries.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation