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

Todoist

Connect Bubble to Todoist using the API Connector plugin with a Private Bearer token header. Todoist's REST API v2 returns lists as bare JSON arrays with no wrapper key, which changes how Bubble's initialization works. Create tasks from Bubble forms using a simple POST call with only the content field required. Embed a full personal task widget in a Bubble CRM or project portal — the free tier works for all plan levels.

What you'll learn

  • How to find and configure a Todoist Personal API token as a Private Bearer header in Bubble API Connector
  • Why Todoist's bare JSON array responses require special handling during Bubble API Connector initialization
  • How to create Todoist tasks from a Bubble form using a simple POST call
  • How to display task priorities and due dates correctly given Todoist's inverted priority scale and nested due object
  • How to build an embedded task widget with project filtering, priority color-coding, and task creation inside a Bubble app
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner16 min read1–2 hoursProductivityLast updated July 2026RapidDev Engineering Team
TL;DR

Connect Bubble to Todoist using the API Connector plugin with a Private Bearer token header. Todoist's REST API v2 returns lists as bare JSON arrays with no wrapper key, which changes how Bubble's initialization works. Create tasks from Bubble forms using a simple POST call with only the content field required. Embed a full personal task widget in a Bubble CRM or project portal — the free tier works for all plan levels.

Quick facts about this guide
FactValue
ToolTodoist
CategoryProductivity
MethodBubble API Connector
DifficultyBeginner
Time required1–2 hours
Last updatedJuly 2026

Embed a Todoist task widget inside your Bubble app

Todoist's simple priority-and-due-date model makes it the most beginner-friendly task API in this category. Its free tier supports full API access with 5 active projects — enough for most early-stage founders to build a personal productivity widget inside a larger Bubble app without any additional subscription cost.

The top Bubble use case for Todoist is an embedded task inbox: a panel inside a Bubble CRM, project portal, or internal tool where users can see their Todoist tasks and create new ones without leaving the Bubble app. This is the kind of integration that takes less than 2 hours to build and immediately saves team members context-switching time.

Two non-obvious details about the Todoist API that affect Bubble setup:

First, Todoist REST API v2 returns list endpoints as bare JSON arrays — just `[{...}, {...}]` with no wrapper key like `data` or `results`. This is different from most APIs. During Bubble API Connector initialization, you must confirm that the response is a list, and in some cases manually set 'Use as' = Data (list) if Bubble misdetects it as a single object.

Second, Todoist's priority scale is inverted: 1 is the lowest priority (normal), and 4 is the highest (urgent). New integrations commonly map this backwards. This tutorial shows you the correct conditional logic to display priority labels and colors.

Integration method

Bubble API Connector

Todoist REST API v2 via Bubble API Connector with a Private Bearer token header. No Bubble marketplace plugin exists for Todoist.

Prerequisites

  • A Todoist account (free tier supports 5 active projects and full API access — no paid plan required for this integration)
  • The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble)
  • Basic familiarity with Bubble's workflow editor, repeating groups, and conditional styling

Step-by-step guide

1

Find your Todoist Personal API token

Log into Todoist at todoist.com. Click your profile avatar in the top-right corner and select 'Settings'. In the Settings page, click the 'Integrations' tab in the left menu. Scroll to the bottom of the Integrations page to find the 'Developer' section. Your Personal API token appears here as a 40-character hexadecimal string. Copy the token. Unlike some APIs, Todoist does not let you generate multiple tokens or revoke individual ones — this single token is tied to your account. If you ever need to invalidate it, you can regenerate it from the same page, but all existing integrations using the old token will stop working immediately. Note that this token never expires unless you regenerate it. It authenticates all API requests as your personal Todoist account, giving access to all your projects, tasks, and labels.

Pro tip: The Todoist API token is personal — it represents your individual Todoist account. If you are building an app for multiple users where each person tracks their own Todoist tasks, each user would need to provide their own token. For a single-user internal tool or a dashboard that shows your own tasks, one shared token works fine.

Expected result: You have a 40-character Todoist API token copied and ready. The Todoist settings page confirms it under Settings → Integrations → Developer.

2

Configure the API Connector with Private Bearer token

In your Bubble app, open the Plugins tab and confirm the API Connector plugin (by Bubble) is installed. Click 'Add another API' and name it 'Todoist'. Set the Authentication to 'Private key in header'. In the key name field, enter `Authorization`. In the value field, enter `Bearer YOUR_TOKEN_HERE` — replace YOUR_TOKEN_HERE with your actual 40-character Todoist API token. Tick the 'Private' checkbox. A Private header is stored encrypted on Bubble's servers and never included in browser-visible requests. Set the base URL to `https://api.todoist.com/rest/v2`. Add a shared header: Content-Type: application/json. Bubble will now proxy all Todoist calls from its servers using your token. You do not need to configure CORS or create any proxy functions — Bubble's server-side execution handles this automatically.

todoist-connector-config.json
1{
2 "api_name": "Todoist",
3 "base_url": "https://api.todoist.com/rest/v2",
4 "headers": [
5 {
6 "key": "Authorization",
7 "value": "Bearer <private>",
8 "private": true
9 },
10 {
11 "key": "Content-Type",
12 "value": "application/json",
13 "private": false
14 }
15 ]
16}

Pro tip: Double-check that the Authorization header value starts with 'Bearer ' (capital B, followed by a space). Missing the space between 'Bearer' and the token is a common formatting error that produces a 401 response from Todoist.

Expected result: The Todoist API group appears in the API Connector plugin with the Authorization header showing a Private lock icon. The base URL shows https://api.todoist.com/rest/v2.

3

Add and initialize the projects and tasks calls

Inside the Todoist API group, click 'Add another call'. Name it 'Get Projects'. Set method to GET and path to `/projects`. Click 'Initialize call'. Todoist returns a bare JSON array: `[{"id": "123", "name": "My Project", ...}, ...]`. There is no wrapper key. Bubble initializes this correctly and detects it as a list. In the 'Use as' dropdown, select 'Data' and tick 'List'. If Bubble tries to set it as a single object, manually switch to List. The data type will be named 'Todoist Get Projects' — rename it to 'Todoist Project' in the Bubble data types panel for cleanliness. Add a second call: 'Get Tasks'. Path: `/tasks`. Add two optional query parameters: - project_id (text, dynamic, optional) - filter (text, dynamic, optional — valid values include 'today', 'overdue', 'p1', '@label_name') For initialization, use a filter value of 'today' as the default. Click 'Initialize call'. Todoist returns the task list including fields: id, content, description, project_id, priority (1-4), due (object with date field), labels (array), and is_completed. In the 'Use as' dropdown, select 'Data' and tick 'List'. Bubble detects the nested `due` object — the `due.date` field contains the task due date as YYYY-MM-DD string.

todoist-api-calls.json
1GET https://api.todoist.com/rest/v2/projects
2Authorization: Bearer <private>
3
4GET https://api.todoist.com/rest/v2/tasks?filter=today
5Authorization: Bearer <private>
6
7// Example task object in response:
8// {
9// "id": "2995104339",
10// "content": "Buy groceries",
11// "description": "",
12// "project_id": "2203306141",
13// "priority": 4,
14// "due": {
15// "date": "2026-07-10",
16// "is_recurring": false
17// },
18// "labels": [],
19// "is_completed": false
20// }

Pro tip: Todoist's filter parameter is powerful for server-side filtering. Instead of loading all tasks and filtering in Bubble (which loads more data and uses more WU), pass filters like 'today', 'overdue', or 'p1' directly to the API. The filter 'p1' means priority=4 tasks (urgent) — remember the inverted priority scale.

Expected result: Two initialized calls in the Todoist API group: Get Projects returns your Todoist projects as a list, Get Tasks returns today's tasks as a list with all fields including the nested due object.

4

Add a task creation call and connect it to a Bubble form

Add a third call: 'Create Task'. Set method to POST and path to `/tasks`. Set body type to JSON. Add the following fields to the body: - content (text, dynamic) — the task title; this is the ONLY required field - project_id (text, dynamic, optional) - due_date (text, dynamic, optional — format YYYY-MM-DD) - priority (number, dynamic, optional — values 1, 2, 3, or 4) - description (text, dynamic, optional) For initialization, enter a real content value ('Test task from Bubble') and click 'Initialize call'. Todoist creates a real task and returns the task object. After initialization, delete the test task from Todoist. On your Bubble page, add: - A text input: 'Task content' (required) - A dropdown: 'Project' with choices from Get Projects call - A date picker: 'Due date' - A dropdown: 'Priority' with choices: Normal (value: 1), Medium (value: 2), High (value: 3), Urgent (value: 4) - A 'Create Task' button Create a workflow triggered by the 'Create Task' button: call 'Todoist - Create Task' with content=Task content input's value, project_id=Project dropdown's value, due_date=Due date picker's value formatted as YYYY-MM-DD, priority=Priority dropdown's value. In the On success event, reset the inputs and refresh the tasks repeating group by calling 'Display data → Get Tasks' again.

todoist-create-task.json
1POST https://api.todoist.com/rest/v2/tasks
2Authorization: Bearer <private>
3Content-Type: application/json
4
5{
6 "content": "<task_content>",
7 "project_id": "<project_id>",
8 "due_date": "<due_date_YYYY-MM-DD>",
9 "priority": <priority_1_to_4>
10}

Pro tip: Todoist's due_date field expects a plain date string in YYYY-MM-DD format. In Bubble, format a date picker's value using the 'formatted as' operator with format string 'YYYY-MM-DD'. Do not include time zone information unless you want to use the due_datetime field instead.

Expected result: Filling in the task form and clicking Create Task adds a new task to Todoist and reloads the tasks repeating group showing the newly created task.

5

Add task completion and build priority color-coding

To mark a Todoist task as complete, add a call: 'Close Task'. Method: POST. Path: `/tasks/{task_id}/close`. Add a path parameter: task_id (text, dynamic). The close endpoint accepts no body and returns a 204 No Content response on success. In your tasks repeating group, add a checkbox element (or a checkmark icon button). Create a workflow: on click, call 'Todoist - Close Task' with task_id = current cell's task's id. On success, refresh the repeating group. For priority color-coding, Todoist uses this scale: - priority = 1 → Normal (show grey badge) - priority = 2 → Medium (show blue badge) - priority = 3 → High (show orange badge) - priority = 4 → Urgent (show red badge) In your repeating group cell, add a Group element sized to a small pill/badge. Add conditionals on this Group: - When current cell's task's priority = 4 → Background color: #EF4444 (red) - When current cell's task's priority = 3 → Background color: #F97316 (orange) - When current cell's task's priority = 2 → Background color: #3B82F6 (blue) - When current cell's task's priority = 1 → Background color: #9CA3AF (grey) Add a text element inside the badge Group with corresponding conditional labels: 'Urgent', 'High', 'Medium', 'Normal'.

todoist-close-task.txt
1POST https://api.todoist.com/rest/v2/tasks/{task_id}/close
2Authorization: Bearer <private>
3// No body needed; returns 204 No Content on success
4
5// Priority mapping (Todoist inverted scale):
6// priority = 1 → Normal (lowest)
7// priority = 2 → Medium
8// priority = 3 → High
9// priority = 4 → Urgent (highest)

Pro tip: After a successful close call, use Bubble's 'Display list' action on the repeating group to re-show the Get Tasks results without the completed task. Alternatively, remove the closed task from a Bubble custom state list immediately for instant UI feedback, then let the full refresh confirm the backend state.

Expected result: Clicking a task's checkbox calls the close endpoint and removes the task from the active tasks view. Priority badges display with color-coded labels (grey Normal, blue Medium, orange High, red Urgent).

6

Handle due dates and add a project-filter dropdown

Todoist due dates live inside a nested `due` object on each task. The `due.date` field is a YYYY-MM-DD string. When a task has no due date, the `due` field itself is null — not an empty object, but null. This means Bubble may show an error or blank text if you reference `task's due's date` without checking for null first. Add a conditional in your due date text element: 'When current cell's task's due is empty, display No due date in grey text; otherwise, display the due's date value.' For overdue detection: compare `due.date` (as a string) with today's date (formatted as YYYY-MM-DD). When due.date < today's date string, the task is overdue. Add a conditional on the row Group: 'When current cell's task's due is not empty AND current cell's task's due's date < Current date/time formatted as YYYY-MM-DD → Background color: light red (#FEE2E2)'. This gives visual overdue alerts. For the projects filter, add a Dropdown above the repeating group. Populate it with the Get Projects call. Add an 'All Projects' option. When the dropdown value changes, update the repeating group data source: 'Get Tasks' with project_id = selected dropdown's value (leave blank for all projects). RapidDev's team builds Todoist widgets embedded in larger Bubble productivity dashboards regularly — if you want to combine this with calendar views, Stripe billing, or team reporting, book a free scoping call at rapidevelopers.com/contact.

todoist-due-date-handling.txt
1// Due date handling in Bubble conditional expressions:
2// Check: 'current cell's task's due is empty'
3// If not empty: 'current cell's task's due's date'
4// Overdue check: compare date string with formatted current date
5
6// Todoist filter parameter examples:
7// filter=today → tasks due today
8// filter=overdue → all overdue tasks
9// filter=p1 → priority 4 (urgent) tasks
10// filter=@work → tasks with 'work' label
11// filter=today | overdue → today and overdue combined

Pro tip: Use Todoist's server-side filter parameter instead of loading all tasks and filtering in Bubble. 'filter=today | overdue' fetches only the urgent-attention tasks in a single API call, which is much more efficient than fetching all tasks and applying Bubble search constraints.

Expected result: Tasks with due dates display the date properly, tasks without due dates show 'No due date' in grey. Overdue tasks highlight in light red. The projects dropdown filters the task list to the selected project.

Common use cases

Personal task inbox panel embedded in a Bubble CRM

Add a collapsible Todoist task panel to a Bubble CRM's main dashboard. Team members see their overdue and today's tasks from Todoist alongside their CRM records. They can mark tasks complete and create new tasks directly from the panel. The filter parameter today or overdue drives smart server-side filtering without loading all tasks.

Bubble Prompt

Create a collapsible sidebar panel in a Bubble CRM page. Add a repeating group with data source from Todoist GET /tasks?filter=today. Inside each row, show the task content text, a priority color badge based on the priority field (4=red, 3=orange, 2=blue, 1=grey), and a checkbox button. On checkbox click, call POST https://api.todoist.com/rest/v2/tasks/{task_id}/close. Add a text input and create button at the bottom to POST new tasks.

Copy this prompt to try it in Bubble

Project task board with priority sorting

Build a Bubble page that shows a Todoist project's tasks sorted by priority (urgent first) with color-coded badges. A projects dropdown lets users switch between their Todoist projects. An add-task form at the top creates new tasks in the selected project. Overdue tasks appear highlighted in red.

Bubble Prompt

Create a Bubble page with a projects dropdown populated by GET https://api.todoist.com/rest/v2/projects. When the selected project changes, reload the tasks repeating group with GET /tasks?project_id={selectedProject}. Sort by priority descending. Apply conditional background colors: priority=4 → red, priority=3 → orange, priority=2 → blue. Add a form with content input, due date picker, and priority dropdown to create new tasks via POST /tasks.

Copy this prompt to try it in Bubble

Daily standup summary from Todoist

Create a Bubble page that generates a daily standup view: tasks completed yesterday, tasks due today, and overdue tasks. Use Todoist's filter parameter with overdue and today values to make three separate API calls. Display the results in three grouped sections, giving team members a quick standup report without opening Todoist.

Bubble Prompt

Create a Bubble page with three repeating groups stacked vertically. First group: GET /tasks?filter=overdue (label: Overdue, highlight red). Second group: GET /tasks?filter=today (label: Due Today). Third group: a completed tasks section using a recent completions query. Add a refresh button that re-runs all three calls. Display each task with content, project name, and due date.

Copy this prompt to try it in Bubble

Troubleshooting

Initialize call returns 401 Unauthorized

Cause: The Todoist API token is invalid, or the Authorization header value is not formatted correctly as 'Bearer TOKEN' with a space.

Solution: Go to Todoist Settings → Integrations → Developer and confirm your API token. In Bubble API Connector, check that the Authorization header value is exactly 'Bearer ' followed by the token with a single space. Copy-paste the token fresh from the Todoist settings page to rule out whitespace issues.

Bubble initializes the task or project list as a single object instead of a list

Cause: Todoist returns bare JSON arrays without a wrapper key. Bubble sometimes misdetects these as single objects rather than lists during initialization.

Solution: After initializing the call, manually set 'Use as' to 'Data' and tick the 'List of...' checkbox in the API Connector initialization dialog. If the initialization completes with a single-object detection, re-initialize the call and explicitly confirm the List option.

'There was an issue setting up your call' error during initialization

Cause: Bubble requires a real successful response with JSON data to detect field types. This error appears if the call returns an error, if no tasks exist for the filter used, or if a required parameter is missing.

Solution: Use a broad filter like 'today' or leave the filter parameter empty (which returns all active tasks) during initialization to ensure there are real tasks to detect. Confirm your API token works by testing the call URL in your browser or with a tool like Postman before initializing in Bubble.

The due date field shows blank or causes an error in the repeating group

Cause: Tasks without a due date return null for the due field (not an empty object). Trying to access due.date on a null value causes an error in Bubble.

Solution: Add a conditional in the due date text element: 'When current cell's task's due is empty, show No due date; otherwise show current cell's task's due's date'. The 'is empty' check in Bubble handles null values correctly.

Priority badges show the wrong label — high-priority tasks appear as low priority

Cause: Todoist's priority scale is inverted from user intuition: 1=Normal (lowest), 4=Urgent (highest). A common mistake is mapping priority=1 as the highest priority.

Solution: Verify your priority conditionals use the correct mapping: priority=4 → Urgent (red), priority=3 → High (orange), priority=2 → Medium (blue), priority=1 → Normal (grey). The number 4 is the most urgent in Todoist.

Close task call returns 404 Not Found

Cause: The task ID being passed to /tasks/{task_id}/close is incorrect or the task was already completed or deleted.

Solution: Confirm you are passing the task's id field (not its project_id or other identifier) as the path parameter. In the repeating group, the reference should be 'current cell's task's id'. Also verify the task exists and has not already been closed.

Best practices

  • Always mark the Todoist Bearer token as Private in Bubble API Connector — it authenticates your entire Todoist account and must never appear in browser network requests.
  • Use Todoist's server-side filter parameter (today, overdue, p1, @label) to pre-filter tasks on the API side rather than loading all tasks and filtering in Bubble. This reduces data transfer and Workload Unit consumption.
  • Handle null due dates explicitly with Bubble conditionals. Tasks without due dates return null for the due field — referencing due.date on a null value will break your repeating group display.
  • Remember that Todoist priority is inverted: 1=Normal, 4=Urgent. Document this mapping clearly in any dropdown choices or conditional labels in your Bubble app.
  • For task deletion, show a confirmation popup before calling DELETE /tasks/{id}. The Todoist API has no undo endpoint — deleted tasks cannot be recovered.
  • Distinguish between closing a task (POST /tasks/{id}/close — marks complete, task is recoverable) and deleting it (DELETE /tasks/{id} — permanent). For most UI interactions, close is the safer default.
  • Add Privacy Rules (Data tab → Privacy) if you store any Todoist task data in Bubble's database, restricting access to the owning user record.

Alternatives

Frequently asked questions

Does Todoist's free tier work for this Bubble integration?

Yes. Todoist's REST API v2 is available on all plans including the free tier. The free tier supports 5 active projects and 5 collaborators — sufficient for building a personal productivity widget or a small-team task inbox. Paid plans (Pro $4/month, Business $6/user/month) add features like reminders, calendar sync, and more collaborators, but none of these are required for the core API integration.

Why does Todoist return a bare array instead of an object with a data field?

Todoist's REST API v2 was designed with a minimal response format — list endpoints return bare JSON arrays like [{...}, {...}] instead of wrapping them in a parent object. This is valid JSON but differs from many other APIs. In Bubble, this means you must explicitly confirm 'Use as = Data (list)' during initialization, rather than relying on Bubble's auto-detection which may interpret the response as a single object.

What is the difference between closing a task and deleting it in Todoist?

Closing a task (POST /tasks/{id}/close) marks it as completed. Completed tasks are kept in Todoist's history and can be un-completed via the Todoist app. Deleting a task (DELETE /tasks/{id}) permanently removes it with no recovery option. For most Bubble UI patterns, use the close endpoint — it is reversible. Only delete tasks when your workflow explicitly requires permanent removal, and always show a confirmation popup first.

How do I display task priority correctly? My urgent tasks show as low priority.

Todoist's priority numbering is inverted from user intuition: priority=1 means Normal (lowest), priority=2 means Medium, priority=3 means High, and priority=4 means Urgent (highest). This is a well-known gotcha documented by Todoist. In Bubble conditionals, map priority=4 to 'Urgent' with red color, and priority=1 to 'Normal' with grey color. The number 4 represents the highest urgency.

Can I use Todoist as a shared task list for multiple Bubble users?

With a single shared API token, all Bubble users would read from and write to the same Todoist account. For a truly multi-user scenario where each person sees their own Todoist tasks, you need each user's individual API token. Store tokens in a Bubble User data type field (with strict privacy rules) and pass the current user's token dynamically in API calls. Todoist also supports shared projects if multiple users are collaborators — all collaborators' tasks in a shared project are visible via any collaborator's token.

How do I filter tasks by label in Bubble?

Use the Todoist filter parameter with the @ prefix for labels: filter=@work filters to tasks with the 'work' label. You can combine filters: filter=today & @work shows today's work-labeled tasks. Alternatively, use the GET /tasks?label_id={id} parameter with the label's ID fetched from GET /labels. The filter parameter approach is simpler for Bubble since it uses natural-language filter strings.

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.