logo
Updated

Logging Todoist Completed Tasks into Reflect through IFTTT v2

This version (previous one here) uses two applets instead of four, with lots of ways to tweak it to fit your style. Once IFTTT lets us use the task ID directly, we'll only need one applet.

This automation requires an IFTTT Pro+ plan.

Last update: 02 FEB 2025

Step 1: Create first applet

You'll set up a web request to send the task ID to the next applet.

Setup

Trigger: New completed task in Todoist.

Filter Code:

// Extract task ID from URL
let taskIdMatch = Todoist.newCompletedTask.LinkToTask.match(/task\/(\d+)/);
let taskId = taskIdMatch ? taskIdMatch[1] : '';

// Send task ID to the second applet
MakerWebhooks.makeWebRequest.setBody(
  JSON.stringify({ value1: taskId })
);

Action: Make a web request to send the task ID to the next applet. We'll configure it later after creating the second applet.

This applet should now look like this:

Step 2: Create second applet

This applet receives the task ID, fetches detailed information using Todoist's Sync API, formats the data, and logs it into Reflect.

Setup

Trigger: Receive a web request. Event name can be anyone like: todoist_completed

Action: Make a web request with JSON response to Todoist's Sync API.

URL: https://api.todoist.com/sync/v9/items/get?item_id= {{Value1}} Notice the space in = {? Yeah, IFTTT does that. Just go with it.

Additional headers: Authorization: Bearer API_TOKEN Grab your API token from Todoist under Settings > Integrations > Developer tab.

Method: GET

Content type: application/json

Filter Code:

// ----- CONFIGURATION OPTIONS -----

// Uncomment the line that matches your preferred date format.
const DATE_FORMAT = 'DD/MM/YYYY';
// const DATE_FORMAT = 'MM/DD/YYYY';

// Display settings
const GROUP_UNDER_SINGLE_LIST = false; // This flag is not compatible with INCLUDE_PARENT_TASK
const SINGLE_LIST_NAME = '[[✅ Completed tasks]]'; // Name for the single task list, if enabled
const USE_ICONS = true; // Toggle icons for tasks

// Define the keyword for cancelled tasks. It has no effect if USE_ICONS is false. Read the guide for more info.
const CANCELLED_KEYWORD = 'cancel';

// Configure flags to include or exclude parts of the task
const INCLUDE_PROJECT = true;
const INCLUDE_SECTION = true;
const INCLUDE_LABELS = true;
const INCLUDE_DESCRIPTION = true;
const INCLUDE_COMMENTS = true;
const INCLUDE_PARENT_TASK = true;
const INCLUDE_CREATION_DATE = true;

// ----- END CONFIGURATION -----

// Format a date in the specified format
function formatDate(date: Date): string {
    const year = date.getFullYear();
    const month = ('0' + (date.getMonth() + 1)).slice(-2);
    const day = ('0' + date.getDate()).slice(-2);
    return DATE_FORMAT
        .replace('YYYY', year.toString())
        .replace('MM', month)
        .replace('DD', day);
}

// Get values from the Todoist Sync API response
const response = JSON.parse(MakerWebhooks.makeWebRequestQueryJson[0].ResponseBody);
const item = response.item;
const parentTask = response.ancestors.length > 0 ? response.ancestors[0] : null;
const notes = response.notes;
const section = response.section;

// Remove "* " from the name of non-completable parent tasks
function cleanTaskName(name: string): string {
    return name.indexOf("* ") === 0 ? name.substring(2) : name;
}

// Check if task is cancelled
function isTaskCancelled(taskName: string): boolean {
    const words = taskName.toLowerCase().split(' ');
    return words[words.length - 1].indexOf(CANCELLED_KEYWORD) !== -1;
}

// Format task's text
const icon = USE_ICONS ? (isTaskCancelled(item.content) ? "◎" : "◉") : "";
const taskNameLink = `${icon ? `[[${icon}]] ` : ""}${cleanTaskName(item.content)} [↗](https://todoist.com/showTask?id=${item.id})`;
const descriptionText = INCLUDE_DESCRIPTION && item.description ? ` ${item.description}` : '';
const labelsText = INCLUDE_LABELS && item.labels.length > 0 ? item.labels.map((label: string) => ` [[${label}]]`).join(' ') : '';
const sectionText = INCLUDE_SECTION && section ? ` [[${section.name}]]` : '';
const projectText = INCLUDE_PROJECT ? ` [[${response.project.name}]]` : ''; // Access project name from response

// Get today's date
const today = new Date();
const todayFormatted = formatDate(today);

// Format task's creation date
const createdDate = new Date(item.added_at);
const formattedCreatedDate = formatDate(createdDate);
const createdDateText = INCLUDE_CREATION_DATE && formattedCreatedDate !== todayFormatted ? ` [[${formattedCreatedDate}` : '';

// Format comments with a pseudo-backlink to their date (Reflect doesn't support backlinks to dates from the API yet) if it's different from today
const notesText = INCLUDE_COMMENTS ? notes.map((note: { posted_at: string; content: string }) => {
    const date = new Date(note.posted_at);
    const formattedDate = formatDate(date);
    const datePrefix = formattedDate !== todayFormatted ? `[[${formattedDate} ` : '';
    const contentLines = note.content.split('\n');
    const indentedContent = contentLines.map((line: string, index: number) => index === 0 ? line : `\t${line}`).join('\n');
    return `- ${datePrefix}${indentedContent}`;
}).join('\n') : '';

// Compose final text of the task
let listName = GROUP_UNDER_SINGLE_LIST ? SINGLE_LIST_NAME : '';
let taskContent = '';

if (INCLUDE_PARENT_TASK && parentTask && !GROUP_UNDER_SINGLE_LIST) {
    const parentLabelsText = parentTask.labels.length > 0 ? parentTask.labels.map((label: string) => ` [[${label}]]`).join(' ') : '';
    const parentLink = `https://todoist.com/showTask?id=${parentTask.id}`;

    // Format parent task's creation date
    const parentCreatedDate = new Date(parentTask.added_at);
    const formattedParentCreatedDate = formatDate(parentCreatedDate);
    const parentCreatedDateText = INCLUDE_CREATION_DATE && formattedParentCreatedDate !== todayFormatted ? ` [[${formattedParentCreatedDate}` : '';
    const parentIcon = USE_ICONS ? "◐" : "";
    const parentTaskContent = `${parentIcon ? `[[${parentIcon}]] ` : ""}${cleanTaskName(parentTask.content)} [↗](https://todoist.com/showTask?id=${parentTask.id})${projectText}${sectionText}${parentLabelsText}${parentCreatedDateText}`;
    taskContent = `${taskNameLink}${descriptionText}${labelsText}${createdDateText}\n${notesText}`; 

    listName = parentTaskContent;
} else {
    taskContent = `${taskNameLink}${descriptionText}${projectText}${sectionText}${labelsText}${createdDateText}\n${notesText}`; 
}

// Compose request body of the call to the Reflect API
const body_request = JSON.stringify({
    text: taskContent,
    transform_type: 'list-append',
    list_name: listName
});

MakerWebhooks.makeWebRequest.setBody(body_request);

Action: Make a web request to log the formatted task details into Reflect.

URL: https://reflect.app/api/graphs/GRAPH_ID/daily-notes Replace GRAPH_ID with your Reflect's graph identifier found in Preferences > Graphs.

Method: PUT

Content type: application/json

Additional headers: Authorization: Bearer ACCESS_TOKEN Replace ACCESS_TOKEN with a Reflect's access token. They can be created here.

This applet should now look like this:

Last step!

Go back to the first applet and configure the action (THEN):

URL: paste the webhook URL of the second applet.

Method: POST

Content type: application/json

Customization

You can easily tweak the filter code in the second applet to make it your own.

Date Format: Adjust the DATE_FORMAT constant to match your regional preference.

Display settings:

Group under the same list: If you want to add your tasks always under a single list, enable this option and give it a name in the next line of the code.

Use icons: It will add this preciding each task: for completed, for cancelled, and for parent tasks. More info below.

Cancelled Keyword: I like to track what I chose not to do and why. Todoist doesn't have a cancelled status, so I add '- cancelada' to the task name before checking it off. If the last word contains that keyword, it's marked as cancelled.

Flags: Use the configuration flags to include or exclude specific parts of the task, such as project, section, labels, description, comments, parent task, and creation date.

How Your Completed Todoist Tasks Will Look in Reflect

Here's a peek at how tasks and comments will show up.

Example 1. Default settings (group under a single list disabled. Icons enabled. All flags enabled).

Task Name Description of the task Project Label1 1/1/2025

1/1/2025 First comment on the task.

[[01/01/25 Another comment before closing its date backlink. More about this below.

Cancelled Task Name Description of the task Project Section 1/1/2025

Task completed in the same day it was created

Parent Task Name Project Section Label1 Label2 1/1/2025

Subtask name Description of the subtask 1/1/2025

1/1/2025 comment on the subtask

Example 2. Group under a single list enabled. Icons disabled. Sections and labels disabled. Creation date disabled.

✅ Completed tasks

Task Name Description of the task Project

1/1/2025 A comment on the task

Last comment made today

Task name

Task name Description of the task

Task name

About this format

Icons:

Icons will help you spot tasks in your daily note if you, like me, prefer to see them flowing with the rest of your daily note and not groped under the same list.

The icons are backlinks, so you can see all the tasks together. Initially, you'll get a note for each one, but you can merge them and add aliases like this: ◉ // ◎ // ◐ // Todoist tasks . if you prefer to keep them separate, consider adding aliases like ◉ // Completed Todoist tasks , ◎ // Cancelled Todoist tasks and ◐ // Parent Todoist tasks to find them easily.

The icon links straight to your task in Todoist.

Parent tasks:

In a subtask, the project and section are only added to the parent to avoid repetition, since they share the same ones.

If you finish a parent task the same day as its subtasks, it will show up twice. I change the icon from ◐ to ◉ and remove the duplicate to keep things tidy.

Date backlinks to daily notes:

Reflect doesn't support creating backlinks to daily notes from the API yet. Having the opening brackets lets you quickly create them by placing the cursor after each date.

The date at the end of a task is its creation date. It's nice to see when something first came to mind.

The date at the beginning of a comment is when it was posted.

Dates are added only if they're not today's date.

That's it. Feel free to share this guide and drop your questions or issues in the Reflect Discord!