# How to Integrate Retool with Todoist

- Tool: Retool
- Difficulty: Beginner
- Time required: 20 minutes
- Last updated: April 2026

## TL;DR

Connect Retool to Todoist using a REST API Resource targeting the Todoist REST API v2 (api.todoist.com/rest/v2). Authenticate with a personal API token from your Todoist account settings. Query tasks, projects, sections, and labels to build a team task management panel that provides cross-project overviews, bulk priority updates, and custom reporting that Todoist's native UI cannot offer.

## Why connect Retool to Todoist?

Todoist's native interface is excellent for individual task management, but teams managing work across multiple projects often need a cross-project view that Todoist does not natively provide. You cannot see all tasks due today across every project in a single sortable table, cannot bulk-update priorities for multiple tasks at once, and cannot combine Todoist task data with data from other systems like a CRM or support tool. Retool bridges this gap by querying the Todoist API and presenting task data in flexible, customizable dashboards.

The most impactful use case is a team task overview that shows all active tasks across all projects in a single Table, filterable by assignee, project, label, due date, and priority. Operations teams and team leads can see at a glance which tasks are overdue, which are due today, and which team members have the highest workload — without asking everyone to send status updates. Retool's Table component supports inline editing, so a manager can update task priorities directly from the Retool dashboard without opening Todoist.

Todoist's REST API v2 is simple and predictable: most resources are queried with GET requests and created or updated with POST/PUT requests, all returning JSON. The key entities are tasks (with content, due date, priority, and assignee), projects (containers for tasks), sections (subdivisions within projects), and labels (cross-project tags). Understanding these relationships allows you to build queries that retrieve exactly the right slice of task data for a particular team or workflow.

## Before you start

- A Todoist account (free or paid — the REST API is available on all plans)
- The Todoist personal API token from Settings → Integrations → Developer → API token
- An understanding of your Todoist project structure (project IDs, labels used by your team)
- A Retool account with Resource creation permissions

## Step-by-step guide

### 1. Find your Todoist API token

Todoist provides a personal API token in your account settings that grants full access to your Todoist data. Unlike OAuth 2.0 flows that require a redirect URI and browser authorization, a personal API token is available immediately and does not expire unless you explicitly regenerate it. In the Todoist web app (todoist.com), click your name or profile picture in the top left corner to open the account menu. Select Settings. In the Settings panel, click the Integrations tab in the left navigation. Scroll to the Developer section at the bottom of the Integrations page. Your API token is displayed in the API token field — it is a 40-character hexadecimal string. Click the copy icon or select all and copy the token. Keep this token secure: it grants full read and write access to your Todoist account including all projects, tasks, and settings. If you are building a team tool in Retool, consider creating a dedicated Todoist account for the integration (e.g., an ops bot account) and sharing the relevant projects with that account, then using its API token — this limits the blast radius if the token is accidentally exposed. If you need to generate a new token (e.g., if the current token was exposed), click the Regenerate Token button in the same Developer section. Regenerating invalidates the old token immediately, so update your Retool configuration variable before regenerating.

**Expected result:** You have a 40-character Todoist API token copied and ready to configure in Retool.

### 2. Configure the Todoist REST API Resource in Retool

In Retool, navigate to the Resources tab in the left sidebar and click Add Resource. Select REST API from the resource type list. Name the resource Todoist API. In the Base URL field, enter https://api.todoist.com/rest/v2 — this is the Todoist REST API v2 base URL. Note that Todoist also has a Sync API at https://api.todoist.com/sync/v9 for batch operations, but for standard CRUD use cases the REST v2 API is simpler and preferred. In the Headers section, click Add Header. Set the header name to Authorization and the value to Bearer {{ config.TODOIST_TOKEN }}. Todoist uses standard Bearer token authentication in the Authorization header, making it one of the most straightforward API authentications to configure in Retool. Create the configuration variable: navigate to Settings → Configuration Variables in Retool. Click Add variable. Name it TODOIST_TOKEN and paste your 40-character API token as the value. Enable the secret toggle to prevent the token from being exposed in frontend JavaScript and to restrict it to resource configurations only. Click Save on the Todoist API Resource. Test the connection by creating a simple GET query with the Path set to /projects — this endpoint returns all projects in your Todoist account and is a reliable health check for the integration. The response should be a JSON array of project objects, each with an id, name, color, and other properties.

**Expected result:** The Todoist API Resource is saved in Retool. A test GET query to /projects returns a JSON array of Todoist projects, confirming the Bearer token authentication is working correctly.

### 3. Query tasks and join with project data

Create a query to fetch all active tasks in Retool. Set Method to GET and Path to /tasks. Todoist's tasks endpoint supports optional filter parameters: add project_id as a URL parameter bound to a project selector dropdown (leave empty to fetch tasks from all projects), and filter as a URL parameter for Todoist filter syntax (e.g., overdue, today, p1 for priority 1, or @label_name for label-filtered tasks). Run the query without filters first to see all active tasks — the response is a JSON array where each task object contains id, content (the task text), project_id, section_id, priority (1=normal, 4=urgent), due (an object with date string), assignee_id, labels (array), url (link to task in Todoist), and is_completed. Because tasks only include project_id and not project_name, you need to join tasks with project data. Create a second GET query targeting /projects to fetch all projects. Create a JavaScript query that combines both datasets: build a lookup map from the projects array (mapping project_id to project_name), then map over the tasks array to add the project_name to each task record. This joined dataset provides all the information needed for a complete task Table without making individual per-task lookups.

```
// JavaScript query: join tasks with project names and section names
// Assumes: getTasks.data = task array, getProjects.data = project array
const tasks = getTasks.data || [];
const projects = getProjects.data || [];

// Build project lookup map
const projectMap = {};
projects.forEach(p => {
  projectMap[p.id] = p.name;
});

// Priority labels for display
const priorityLabel = { 1: 'Normal', 2: 'Medium', 3: 'High', 4: 'Urgent' };
const priorityColor = { 1: '#808080', 2: '#246fe0', 3: '#eb8909', 4: '#d1453b' };

const today = new Date().toISOString().split('T')[0];

return tasks.map(task => {
  const dueDate = task.due?.date || null;
  const isOverdue = dueDate && dueDate < today && !task.is_completed;

  return {
    id: task.id,
    content: task.content,
    project_name: projectMap[task.project_id] || 'Inbox',
    project_id: task.project_id,
    priority: task.priority,
    priority_label: priorityLabel[task.priority] || 'Normal',
    due_date: dueDate || '',
    due_display: dueDate ? dueDate : 'No due date',
    is_overdue: isOverdue,
    assignee_id: task.assignee_id || '',
    labels: task.labels?.join(', ') || '',
    url: task.url || ''
  };
}).sort((a, b) => {
  // Sort overdue first, then by priority descending
  if (a.is_overdue && !b.is_overdue) return -1;
  if (!a.is_overdue && b.is_overdue) return 1;
  return b.priority - a.priority;
});
```

**Expected result:** The JavaScript query returns a joined array of task objects with content, project_name, priority_label, due_date, and is_overdue flag. Tasks are sorted with overdue items first, then by priority. The data is ready to bind to a Table component.

### 4. Build the task management Table with filters and inline actions

With the joined task dataset available, build the task management interface in Retool. Drag a Table component onto the canvas and bind its data to {{ joinedTasks.data }}. Configure the Table columns: show content (rename to Task), project_name (rename to Project), priority_label (rename to Priority), due_display (rename to Due Date), and labels. Hide technical fields like id, project_id, and url from the Table view but keep them in the data for use in action buttons. Enable column sorting on the Table so users can sort by project, priority, or due date. Add conditional row styling: for rows where is_overdue is true, apply a red background color using Retool's conditional formatting rules on the Table. Add a Segmented Control or Button Group at the top with options: All Tasks, Due Today, Overdue, High Priority. Wire each option to a different Todoist filter parameter in the tasks query. Add a project filter dropdown bound to {{ getProjects.data }} so users can narrow to a single project. Add action buttons to the Table: a Complete button that fires a POST query to /tasks/{{ self.id }}/close, which marks the task complete in Todoist. Add a confirmation dialog before closing tasks. Add a link-out button using the url field to open the task directly in Todoist for editing. Add a Stat component row at the top showing: Total Active Tasks, Due Today count, and Overdue count — compute these from the task array in a JavaScript query.

```
// JavaScript query: compute task summary stats for Stat components
const tasks = joinedTasks.data || [];
const today = new Date().toISOString().split('T')[0];

const overdue = tasks.filter(t => t.is_overdue).length;
const dueToday = tasks.filter(t => t.due_date === today && !t.is_overdue).length;
const highPriority = tasks.filter(t => t.priority >= 3).length;
const urgent = tasks.filter(t => t.priority === 4).length;

return {
  total: tasks.length,
  overdue: overdue,
  due_today: dueToday,
  high_priority: highPriority,
  urgent: urgent
};
```

**Expected result:** A complete task management Table displays all tasks with project names, priority labels, and due dates. Overdue tasks are highlighted in red. Filter controls narrow tasks by project and status. The Complete action marks tasks done in Todoist when triggered. Summary Stats show task counts at the top.

### 5. Add task creation form

Extend the dashboard with a task creation panel so users can add new Todoist tasks directly from Retool without switching to the Todoist app. Drag a Form component onto the canvas (or use a Container with individual input components). Add a TextInput for the task content (label it Task Name, make it required). Add a Select component for the project — bind its options to {{ getProjects.data.map(p => ({label: p.name, value: p.id})) }}. Add a DatePicker for the due date. Add a Select for priority with options: Normal (1), Medium (2), High (3), Urgent (4). Add a TextInput for labels (comma-separated). Wire the Form's submit button to a POST query: set Method to POST, Path to /tasks, and set the Body to JSON format with the following fields: content referencing the task name input value, project_id referencing the project select value, due_date referencing the date picker formatted as YYYY-MM-DD, priority referencing the priority select value as a number, and labels as an array split from the labels input. In the POST query's Success event handler, trigger the getTasks query to refresh the task list and show a success notification. Add a separate Close/Delete query for task management: set Method to DELETE with Path to /tasks/{{ tasksTable.selectedRow.id }} wired to a Delete button, with a confirmation modal before executing. For production team tools requiring Todoist integration alongside CRM, project management, or support systems in a unified Retool workspace, RapidDev's team can help architect a comprehensive internal tool solution.

```
// POST query body for creating a new Todoist task
// Method: POST, Path: /tasks
// Set Body type to JSON
{
  "content": "{{ taskNameInput.value }}",
  "project_id": "{{ projectSelect.value }}",
  "due_date": "{{ formatDate(dueDatePicker.value, 'YYYY-MM-DD') }}",
  "priority": {{ prioritySelect.value || 1 }},
  "labels": {{ labelsInput.value ? labelsInput.value.split(',').map(l => l.trim()) : [] }},
  "description": "{{ descriptionInput.value || '' }}"
}
```

**Expected result:** The task creation Form successfully creates new tasks in Todoist when submitted. The task list Table refreshes automatically. A success notification confirms creation. The Delete action removes tasks after confirmation.

## Best practices

- Store the Todoist API token in a Retool configuration variable marked as secret — this prevents the token from appearing in frontend JavaScript context and makes credential rotation straightforward.
- Use Todoist's server-side filter syntax (the filter URL parameter) to pre-filter tasks rather than fetching all tasks and filtering in a JavaScript transformer — server-side filtering is faster and reduces response payload size for accounts with many tasks.
- Join task data with project names in a single JavaScript query rather than making individual API calls per task — build a project lookup map once from the /projects endpoint and use it to enrich all tasks in a single map() pass.
- Cache the projects query aggressively (30+ minutes) since project names change infrequently — only the tasks query needs frequent refreshing to show current task status.
- Use Todoist's priority integer values (1-4) internally for sorting and filtering, but display human-readable labels (Normal, Medium, High, Urgent) in the Table — this makes priority columns sortable numerically while remaining readable to users.
- Add confirmation dialogs before Close and Delete task actions — accidentally closing tasks in a shared team project affects all team members and cannot be automatically undone through the API.
- For team Todoist accounts, consider creating a dedicated service account with access only to team projects and using its API token in Retool — this limits data exposure to only the projects relevant to the internal tool.

## Use cases

### Cross-project task overview for team leads

Build a Retool dashboard showing all active tasks across all projects, with columns for task name, project, assignee, due date, and priority. Filter controls let the team lead narrow by assignee, project, label, or overdue status. The Table supports inline priority editing so leads can reprioritize tasks without opening Todoist. A summary panel at the top shows total task counts by priority level and count of overdue tasks.

Prompt example:

```
Build a Retool Todoist task overview that queries all active tasks via the Todoist REST API v2 and joins them with project names. Display a Table with task content, project name, assignee name, due date, and priority (1-4). Add filter dropdowns for project and assignee, and highlight overdue tasks (due date before today) in red. Show a summary row at the top with total tasks, overdue count, and breakdown by priority.
```

### Daily standup dashboard with tasks due today

Create a Retool standup dashboard that shows each team member's tasks due today or overdue, grouped by assignee. The dashboard refreshes automatically and displays a live view of what everyone is working on for the current day. A toggle switch lets the user switch between Today, This Week, and Overdue views. The panel replaces manual status update meetings by making current task status always visible.

Prompt example:

```
Build a Retool daily standup board that queries Todoist tasks filtered by due date of today or earlier. Group the Table by assignee_id and show task content, project name, priority, and due date. Add a segment control switching between Today, This Week, and Overdue filters. Include a Stat component showing total tasks due today and count by team member.
```

### Task creation and management panel

Build a Retool panel with a Form for creating new Todoist tasks from an internal tool. The Form includes fields for task content, project selection (from a dropdown populated by the projects API), due date, priority level, and label assignment. On submit, the Form posts to Todoist's task creation endpoint. The Panel also shows recently created tasks and allows closing or deleting tasks with confirmation dialogs.

Prompt example:

```
Build a Retool Todoist task management panel with a Form for creating new tasks. Populate the project dropdown from the Todoist projects API. Include fields for content, due date, priority (1-4), and project selection. On form submit, POST to the Todoist tasks endpoint and refresh the task list. Show the last 20 created tasks in a Table below the form with a Close button for each.
```

## Troubleshooting

### GET /tasks returns an empty array even though tasks exist in Todoist

Cause: The API token belongs to a Todoist account that does not have access to the projects containing the tasks, or the tasks are in a project shared with another account whose token is being used.

Solution: Test the API token by querying /projects first — if it returns an empty array, the token is for a different account than expected. Confirm you are using the API token for the Todoist account that owns or has access to the target projects. For shared team projects, ensure the account whose token you are using is a member of those projects.

### POST /tasks returns a 400 Bad Request error when creating a task

Cause: The request body is missing required fields, fields have incorrect types (priority must be an integer, not a string), or the project_id does not exist or is not accessible to the token account.

Solution: Ensure content is a non-empty string — it is the only required field. Check that priority is sent as an integer (1-4), not a string. Verify project_id is a valid project ID from the /projects endpoint. If no project_id is specified, Todoist creates the task in the Inbox. View the raw response body in Retool's query inspector for the specific validation error message.

### POST /tasks/id/close returns 404 Not Found for a task that appears in the Table

Cause: The task ID used in the URL path is from a previous data fetch and the task was already completed or deleted in Todoist, or the task ID is being incorrectly interpolated in the URL.

Solution: Verify the task ID is correctly referenced in the URL: Path should be /tasks/{{ tasksTable.selectedRow.id }} and the id field must be populated in the Table data. Refresh the task list before attempting to close tasks. Check that the id column is included in the Table data source (it may be hidden from display but must be present in the data binding).

### Task due dates show incorrectly or are off by one day in the Table

Cause: Todoist returns due dates in YYYY-MM-DD format (date-only) without timezone information. JavaScript's new Date() constructor interprets date-only strings as UTC midnight, which displays as the previous day in non-UTC timezones.

Solution: When displaying due dates, parse them as local dates by appending T12:00:00 before passing to Date constructor, or simply display the raw YYYY-MM-DD string as-is rather than converting it to a Date object. For due date comparison (checking overdue status), compare against today's date in local timezone using new Date().toLocaleDateString('en-CA') to get YYYY-MM-DD format.

```
// Safe date comparison for overdue detection
const today = new Date();
const todayStr = today.getFullYear() + '-' +
  String(today.getMonth() + 1).padStart(2, '0') + '-' +
  String(today.getDate()).padStart(2, '0');
const isOverdue = task.due?.date && task.due.date < todayStr;
```

## Frequently asked questions

### Does the Todoist API token expire, and do I need to rotate it?

Todoist personal API tokens do not expire automatically — the same token remains valid indefinitely unless you explicitly regenerate it from Settings → Integrations → Developer. You should regenerate the token if you suspect it has been compromised. After regenerating, update the TODOIST_TOKEN configuration variable in Retool immediately, since the old token becomes invalid the moment you regenerate.

### Can I use the Retool integration with team-shared Todoist projects?

Yes. Todoist shared projects are accessible through the API to any team member who has been invited to the project. The API token's account must be a member of the shared project. For a team Retool integration, using a token from an admin account ensures access to all team projects. Tasks returned from shared projects include assignee_id to identify which team member is responsible.

### What is the difference between Todoist's REST API v2 and the Sync API?

The REST API v2 (api.todoist.com/rest/v2) is a standard CRUD REST API optimized for individual resource operations — creating a task, reading projects, closing a task. The Sync API (api.todoist.com/sync/v9) is designed for efficient bulk synchronization: it accepts a list of commands in a single request and uses a sync_token to fetch only changes since the last sync. For Retool dashboards that primarily read and display data, the REST API v2 is simpler and more appropriate. Use the Sync API only if you need high-frequency bulk operations.

### How do I filter tasks by assignee in a shared Todoist project?

Todoist's /tasks endpoint supports a filter parameter using Todoist's filter syntax. To filter by assignee, use 'assigned to: username' where username is the display name of the team member. Alternatively, fetch all tasks and filter in a JavaScript transformer using the assignee_id field — this approach lets you populate a Retool Select dropdown from the task data and filter dynamically without making a new API call per filter change.

### Can Retool create recurring Todoist tasks?

Yes. When creating a task via POST /tasks, set the due_string field to a human-readable recurrence description like 'every day at 9am', 'every Monday', or 'every 2 weeks'. Todoist's natural language parser interprets these strings and sets the appropriate recurrence pattern. The due_date field is for one-time due dates; due_string accepts natural language input including recurrence descriptions.

---

Source: https://www.rapidevelopers.com/retool-integrations/todoist
© RapidDev — https://www.rapidevelopers.com/retool-integrations/todoist
