Last active
November 8, 2025 10:00
-
-
Save pookdeveloper/8b1f655e75eb700031b4f6181b3e6fb8 to your computer and use it in GitHub Desktop.
add today to todoist using google script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Google Apps Script to automate tasks in Todoist. | |
| * | |
| * Goal: | |
| * 1. List all active tasks **ONLY from the Inbox**. | |
| * 2. Assign today’s date to Inbox tasks that have no due date. | |
| * 3. Note: The delete function is included, but the REST API V2 does NOT | |
| * allow automatic listing of completed (archived) tasks for deletion. | |
| * The script can only handle active tasks. | |
| */ | |
| // ===================================================================== | |
| // 1. CONFIGURATION (REPLACE WITH YOUR TOKEN) | |
| // ===================================================================== | |
| // Get your token from Settings > Integrations > Developer in the Todoist web app. [3] | |
| const TODOIST_TOKEN = "x-x-x-x-x-x-x"; | |
| const API_BASE_URL = "https://api.todoist.com/rest/v2/"; | |
| const API_TASKS_ENDPOINT = API_BASE_URL + "tasks"; | |
| const API_PROJECTS_ENDPOINT = API_BASE_URL + "projects"; | |
| // Authentication headers (Bearer Token) | |
| const API_HEADERS = { | |
| Authorization: "Bearer " + TODOIST_TOKEN, | |
| // Required for requests that send data (POST/DELETE) | |
| "Content-Type": "application/json", | |
| }; | |
| // Global variable to store the Inbox ID once retrieved | |
| let INBOX_PROJECT_ID = null; | |
| // ===================================================================== | |
| // 2. DATE UTILITIES | |
| // ===================================================================== | |
| /** | |
| * Generates today’s date in YYYY-MM-DD format, as required by the Todoist API. [4] | |
| * Uses the script’s timezone to ensure local accuracy. [5] | |
| * @returns {string} Today’s date in 'YYYY-MM-DD' format. | |
| */ | |
| function getTodayDateString() { | |
| const date = new Date(); | |
| const timeZone = Session.getScriptTimeZone(); // Get the script’s timezone | |
| return Utilities.formatDate(date, timeZone, "yyyy-MM-dd"); // Required format [4] | |
| } | |
| // ===================================================================== | |
| // 3. API WRAPPER | |
| // ===================================================================== | |
| /** | |
| * Core function to interact with the Todoist API using UrlFetchApp. | |
| * Handles authentication, JSON parsing, and special response codes (204). [6] | |
| * | |
| * @param {string} url - Full endpoint URL. | |
| * @param {string} method - HTTP method (GET, POST, DELETE). | |
| * @param {Object} [payload] - JSON body for POST requests. | |
| * @returns {Object|null} JSON response object or null in case of failure or 204. | |
| */ | |
| function fetchTodoistApi(url, method, payload) { | |
| const options = { | |
| method: method, | |
| headers: API_HEADERS, | |
| // Allows the script to manually handle HTTP 4xx and 5xx error codes. [6] | |
| muteHttpExceptions: true, | |
| }; | |
| if (payload && method !== "GET") { | |
| options.payload = JSON.stringify(payload); | |
| } | |
| try { | |
| const response = UrlFetchApp.fetch(url, options); | |
| const responseCode = response.getResponseCode(); | |
| if (responseCode === 200) { | |
| // Success with content (typically for GET) [4] | |
| return JSON.parse(response.getContentText()); | |
| } else if (responseCode === 204) { | |
| // Success with no content (typically for POST/DELETE) [7] | |
| return { | |
| success: true, | |
| code: 204 | |
| }; | |
| } else if (responseCode === 401) { | |
| // Authentication error | |
| Logger.log("ERROR 401: Invalid or expired Todoist token. Stopping execution."); | |
| throw new Error("ERROR 401: Authorization failed."); | |
| } else { | |
| // Other 4xx or 5xx errors | |
| Logger.log(`ERROR ${responseCode} calling ${url}. Message: ${response.getContentText()}`); | |
| return null; | |
| } | |
| } catch (e) { | |
| Logger.log("Exception in fetchTodoistApi: " + e.message); | |
| return null; | |
| } | |
| } | |
| // ===================================================================== | |
| // 4. BUSINESS LOGIC FUNCTIONS | |
| // ===================================================================== | |
| /** | |
| * Retrieves the ID of the Inbox project by querying | |
| * the /projects endpoint and finding the one with "is_inbox_project": true. | |
| * @returns {string|null} The Inbox project ID or null if not found. | |
| */ | |
| function getInboxProjectId() { | |
| if (INBOX_PROJECT_ID) { | |
| return INBOX_PROJECT_ID; // Use cached value if available | |
| } | |
| Logger.log("Searching for Inbox project ID..."); | |
| const projects = fetchTodoistApi(API_PROJECTS_ENDPOINT, "GET"); | |
| if (projects && Array.isArray(projects)) { | |
| const inboxProject = projects.find(project => project.is_inbox_project === true); [2] | |
| if (inboxProject) { | |
| INBOX_PROJECT_ID = inboxProject.id; | |
| Logger.log(`Inbox ID found: ${INBOX_PROJECT_ID}`); | |
| return INBOX_PROJECT_ID; | |
| } | |
| } | |
| Logger.log("ERROR: Could not retrieve Inbox project ID."); | |
| return null; | |
| } | |
| /** | |
| * Updates the due date of a specific task to today. | |
| * @param {string} taskId - ID of the task to update. | |
| * @param {string} todayDateString - Date in 'YYYY-MM-DD' format. | |
| * @returns {boolean} True if the update was successful. | |
| */ | |
| function updateTaskDueDate(taskId, todayDateString) { | |
| const url = `${API_TASKS_ENDPOINT}/${taskId}`; | |
| const payload = { | |
| due_date: todayDateString, // Use due_date in YYYY-MM-DD format [4] | |
| }; | |
| const result = fetchTodoistApi(url, "POST", payload); | |
| if (result && result.code === 204) { | |
| Logger.log(` Task ${taskId} updated with today’s date: ${todayDateString}`); | |
| return true; | |
| } else { | |
| Logger.log(`[FAILED] Could not update task ${taskId}.`); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Deletes a specific active task. | |
| * NOTE: This function only applies to ACTIVE tasks. [7] | |
| * @param {string} taskId - ID of the task to delete. | |
| * @returns {boolean} True if deletion was successful. | |
| */ | |
| function deleteActiveTask(taskId) { | |
| const url = `${API_TASKS_ENDPOINT}/${taskId}`; | |
| const result = fetchTodoistApi(url, "DELETE"); [7] | |
| if (result && result.code === 204) { | |
| Logger.log(` Active task ${taskId} deleted successfully.`); | |
| return true; | |
| } else { | |
| Logger.log(`[DELETE FAILURE] Could not delete active task ${taskId}.`); | |
| return false; | |
| } | |
| } | |
| // ===================================================================== | |
| // 5. MAIN AUTOMATION FUNCTION | |
| // ===================================================================== | |
| /** | |
| * Main function to execute (can be scheduled via a trigger). | |
| * Iterates over ACTIVE INBOX tasks and assigns today’s date to those without one. | |
| */ | |
| function runTodoistAutomation() { | |
| Logger.log("--- STARTING TODOIST AUTOMATION (INBOX ONLY) ---"); | |
| // 1. Get the Inbox ID to filter tasks | |
| const inboxId = getInboxProjectId(); | |
| if (!inboxId) { | |
| Logger.log("CRITICAL ERROR: Cannot proceed without Inbox ID."); | |
| return; | |
| } | |
| // 2. Get today’s date in the proper format | |
| const todayDate = getTodayDateString(); | |
| Logger.log(`Today’s date to assign: ${todayDate}`); | |
| // 3. Build the URL with the 'project_id' parameter to filter by Inbox [4] | |
| // This parameter replaces the text filter 'filter=inbox' that caused ERROR 400. | |
| const urlWithFilter = `${API_TASKS_ENDPOINT}?project_id=${inboxId}`; | |
| // 4. Retrieve all active (pending) Inbox tasks [4] | |
| const tasks = fetchTodoistApi(urlWithFilter, "GET"); | |
| if (!tasks || tasks.length === 0) { | |
| Logger.log("No active Inbox tasks found or API call failed."); | |
| return; | |
| } | |
| Logger.log(`Active tasks found in Inbox: ${tasks.length}`); | |
| let updatedCount = 0; | |
| let deletedCount = 0; | |
| // 5. Iterate and process tasks | |
| tasks.forEach(task => { | |
| // A. Logic: Assign today’s date to tasks with no due date | |
| // The 'due' property is null when the task has no assigned date. | |
| if (task.due === null) { | |
| Logger.log(` Task: ${task.content} (ID: ${task.id}) has no date. Assigning today’s date.`); | |
| if (updateTaskDueDate(task.id, todayDate)) { | |
| updatedCount++; | |
| } | |
| } | |
| // B. Logic: Delete completed tasks (applies only to active ones) | |
| /* | |
| * Limitation: The REST API V2 does not expose the completed task history | |
| * for bulk deletion. | |
| */ | |
| }); | |
| Logger.log(`--- PROCESS COMPLETED ---`); | |
| Logger.log(`Inbox tasks updated with today’s date: ${updatedCount}`); | |
| Logger.log(`Inbox tasks deleted (requires additional filter logic for active ones): ${deletedCount}`); | |
| } | |
| // ===================================================================== | |
| // 6. USAGE INSTRUCTIONS FOR GOOGLE APPS SCRIPT | |
| // ===================================================================== | |
| // 1. Paste this code into the 'Code.gs' file. | |
| // 2. Replace `TODOIST_TOKEN` with your personal key. [3] | |
| // 3. **Set up the Trigger:** Schedule the `runTodoistAutomation` function to run daily. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment