2026-05-07

Managing Recurring Tasks in Obsidian with DataviewJS: Complete Guide

Learn how to automate and track repeating workflows by managing recurring tasks in Obsidian with DataviewJS. Step-by-step code snippets and logic explained.

Editor summary

Recurring Tasks Obsidian Dataviewjs matters because Managing Recurring Tasks in Obsidian with DataviewJS: Complete Guide turns Managing Recurring Tasks in Obsidian with DataviewJS: Complete Guide into a concrete operating decision instead of a loose idea. I would pay closest attention to The Anatomy of an Obsidian Recurring Task, because that detail affects whether the setup survives contact with a real Obsidian vault. The caution is to trial the advice on one representative project before standardizing it; plugin settings, file structure, hardware constraints, or team habits can change the result quickly. That small test makes the recommendation easier to verify and prevents a clean-looking setup from creating cleanup work later.

Recurring task planning desk with visible sticky notes
A planning desk with sticky notes, used to represent workflow mapping and hand-picked editorial links. Photo: Anastasia Shuraeva / Pexels Source

Managing Recurring Tasks in Obsidian with DataviewJS: Complete Guide

Quick Answer: Managing recurring tasks in Obsidian with DataviewJS involves storing task recurrence intervals in inline fields (e.g., [repeat:: daily]), and writing a DataviewJS query that automatically calculates the next due date based on the completion date. This method prevents vault clutter by dynamically rendering upcoming tasks on a dashboard without generating duplicate markdown files for every instance of a repeating task.

Managing tasks directly within Obsidian keeps your knowledge base and your action items perfectly aligned. However, standard markdown checkboxes lack the logic required to handle tasks that repeat daily, weekly, or monthly. While plugins exist for task management, relying entirely on DataviewJS gives you absolute control over how tasks are stored, queried, and displayed, without being locked into a rigid third-party data structure.

By utilizing DataviewJS, you can transform static markdown files into a dynamic task management system. You leverage standard JavaScript to parse task metadata, calculate temporal offsets, and render customized task lists that automatically roll over when completed. This approach is highly efficient for users who want to avoid creating hundreds of redundant notes for daily chores or weekly reviews.

This guide details the structural logic, the metadata requirements, and the exact JavaScript code needed to build a robust recurring task system natively within your Obsidian vault.

For a lower-code baseline before writing custom JavaScript, compare this approach with a dedicated Obsidian Tasks plugin setup so you know which responsibilities should stay in DataviewJS and which can be delegated to a task plugin.

The Anatomy of an Obsidian Recurring Task

Before writing any JavaScript, the underlying task data must be structured predictably. Dataview relies on metadata, which can be formatted as YAML frontmatter or inline fields. For task management, inline fields attached directly to the markdown checkbox are the most reliable method.

A standard task looks like this: - [ ] Clean the coffee machine

To make it recurring, we append inline metadata that our DataviewJS script will later parse. We need two primary fields: the base due date and the repetition interval.

- [ ] Clean the coffee machine [due:: 2026-05-07] [repeat:: 1 weeks]

This specific syntax allows Dataview to index the parameters at the exact line level of the task, rather than at the page level. This is crucial if a single markdown document—such as a project note or a daily daily—contains multiple tasks with different recurring schedules.

Supported Interval Formats

To ensure our JavaScript logic can easily parse the repetition interval, standardizing the text string is necessary. We will map these strings to moment.js duration parameters (which Obsidian includes by default).

  • 1 days or daily
  • 2 weeks or biweekly
  • 1 months or monthly
  • 1 years or annually

By keeping the format as [number] [unit], the parsing logic remains lightweight.

Writing the Base DataviewJS Query

To display these tasks dynamically, you will use a DataviewJS code block. The first step is querying your vault for incomplete tasks that contain a repeat field.

Filtering early is a critical performance optimization. Instead of pulling every page in the vault and then looking for tasks, we instruct Dataview to only target specific folders or tags.

// Step 1: Define the scope
const taskPages = dv.pages('"Projects" or "Routines"');

// Step 2: Filter for specific task criteria
const recurringTasks = taskPages.file.tasks
    .where(t => !t.completed)
    .where(t => t.repeat);

This snippet isolates only the tasks that are currently unchecked and have the repeat metadata attached. If you manage a vault with tens of thousands of files, narrowing the dv.pages() query to a specific directory (like "Routines") reduces execution time from hundreds of milliseconds to under ten milliseconds.

Calculating Next Due Dates Dynamically

The core challenge of managing recurring tasks in Obsidian with DataviewJS is date math. When a user looks at a task dashboard, the system must recognize if a recurring task is overdue, due today, or due in the future.

Obsidian exposes moment.js globally, which makes date manipulation straightforward. We need to extract the due date, compare it to the current date, and render it accordingly.

const today = moment().startOf('day');

const processedTasks = recurringTasks.map(task => {
    // Check if the task has a defined due date
    let dueDate = task.due ? moment(task.due.toString()) : null;
    
    // If there is no due date, or it's improperly formatted, fall back to today
    if (!dueDate || !dueDate.isValid()) {
        dueDate = today;
    }
    
    // Calculate urgency
    const daysUntilDue = dueDate.diff(today, 'days');
    let urgencyStatus = "";
    
    if (daysUntilDue < 0) {
        urgencyStatus = "🔴 Overdue";
    } else if (daysUntilDue === 0) {
        urgencyStatus = "🟢 Due Today";
    } else {
        urgencyStatus = `⚪ Due in ${daysUntilDue} days`;
    }
    
    // Attach the calculated status to the task object for rendering
    task.visualStatus = urgencyStatus;
    task.realDueDate = dueDate;
    
    return task;
});

This mapping function loops through the filtered tasks, normalizes the dates using moment(), calculates the exact differential in days, and assigns a visual indicator.

Handling the Completion and Rollover Logic

The most frequent hurdle when building this workflow is task completion. Standard Obsidian behavior strikes through a task when checked. If you check off - [ ] Clean the coffee machine [due:: 2026-05-07] [repeat:: 1 weeks], it becomes - [x].

If it remains - [x], DataviewJS will simply ignore it, and the task will never recur.

There are two primary ways to handle the rollover:

Approach 1: The Templater Automation (Permanent Record)

You can use the Obsidian Templater plugin combined with a hotkey to process task completion. When you run a specific Templater script on a line, it checks the task, reads the repeat string, calculates the new date (e.g., adding 7 days to 2026-05-07), and appends a brand new unchecked task directly below the original one. This method maintains a historical log of every time you completed the task.

Approach 2: The DataviewJS “Phantom” Task (Clean Vault)

If you do not care about historical logs and just want to know what to do next, you can use DataviewJS to calculate the future date on the fly based on the file’s modification time or a manual date update. However, standard practice for Dataview-only setups is to simply alter the inline field manually. You check the box, and a background script (like the MetaEdit API or an internal Dataview hook) updates the due field and unchecks the box.

For a purely native DataviewJS approach without external automation plugins, you render the list sorted by due date. When you complete the task, you manually update the due:: string to the next date. While slightly manual, it prevents vault bloat.

Rendering the Task Dashboard

Once the data is mapped and sorted, you must render it cleanly. Dataview provides dv.taskList(), which renders standard Obsidian checkboxes that can be clicked directly from the query result.

// Sort tasks by due date
const sortedTasks = processedTasks.sort(t => t.realDueDate);

// Render the final interactive list
dv.header(3, "Upcoming Recurring Tasks");
dv.taskList(sortedTasks, false);

By passing false as the second argument to dv.taskList(), we tell Dataview not to group the tasks by their origin file, creating a single, consolidated action list.

If you want a more tabular, dashboard-style view, you can bypass dv.taskList and render an HTML table using dv.table(). Note that tasks rendered in a table lose their interactive checkbox functionality and become read-only text.

dv.table(["Task", "Status", "Source"], 
    sortedTasks.map(t => [
        t.text, 
        t.visualStatus, 
        dv.fileLink(t.path)
    ])
);

Advanced Filtering: Weekdays and Exclusions

Basic intervals like 1 weeks are simple, but many recurring tasks operate on specific days, such as “Every weekday” or “First Monday of the month.”

DataviewJS can handle complex recurrence via custom inline fields. Instead of [repeat:: 1 weeks], you can use [repeatDays:: 1,2,3,4,5], representing Monday through Friday.

// Example logic for finding the next valid weekday
function getNextWeekday(currentDate) {
    let nextDate = moment(currentDate).add(1, 'days');
    // 0 is Sunday, 6 is Saturday
    while (nextDate.day() === 0 || nextDate.day() === 6) {
        nextDate.add(1, 'days');
    }
    return nextDate;
}

By embedding custom functions inside your DataviewJS block, you can execute logic that no off-the-shelf task plugin can match. You can exclude holidays (by cross-referencing a list of dates in your vault) or pause recurrences automatically if a specific “Vacation” note exists.

Practical Tradeoffs and Performance Parameters

When choosing between managing recurring tasks in Obsidian with DataviewJS versus a dedicated plugin like the Obsidian Tasks plugin, consider the following constraints and performance dimensions:

  • Vault Scale limitations: Dataview must parse the entire vault structure on application load. If your vault exceeds 5,000 markdown files, querying unindexed inline fields can delay the render of your task dashboard by 300ms to 800ms. Always restrict dv.pages() to a dedicated /Tasks or /Management directory.
  • Mobile execution: DataviewJS runs perfectly on the Obsidian mobile app (iOS and Android). However, heavy JavaScript date manipulations block the main thread. Keep your task queries localized to the current file or specific folders to prevent UI stutter when opening the app.
  • Data portability: Inline fields (e.g., [due:: ...]) are inherently proprietary to the Dataview ecosystem. If you ever migrate away from Obsidian, these fields remain as plain text in your markdown, which is readable, but breaking the JavaScript parsing logic.
  • Historical tracking: As mentioned in the completion logic, native DataviewJS struggles with mutating source files directly. If maintaining an audit trail of completed recurring tasks is a strict requirement for your workflow, combining DataviewJS for visualization with Templater for file mutation is the optimal architectural pattern.

Synthesizing the Dataview Task Workflow

Implementing recurring task logic using DataviewJS allows you to tailor your productivity system precisely to your mental model. By structuring your metadata cleanly with inline fields, utilizing moment.js for robust date math, and rendering interactive dashboards, you eliminate the friction of managing repeating chores. The tradeoff is the initial setup time and understanding the underlying JavaScript. However, once established, this customized approach requires significantly less maintenance than manually creating daily notes or duplicating task lists.

Frequently Asked Questions

Does DataviewJS automatically create a new task file when I check one off?

No. DataviewJS is primarily a read-only querying engine. When you check a box in a Dataview list, it updates the status of the source task to completed. It does not automatically generate a new markdown line or file unless you pair it with a scripting tool like Templater or the MetaEdit plugin.

How do I handle tasks that repeat on specific weekdays?

You can create a custom inline field like [weekdaysOnly:: true] and write a JavaScript function within your DataviewJS block. The script will use moment().day() to check if the next calculated due date falls on a weekend, and if so, mathematically push the due date forward to the following Monday.

Can I sync these recurring tasks to Google Calendar?

Not directly through DataviewJS alone. DataviewJS operates strictly within the Obsidian environment. To sync these tasks, you would need an intermediary plugin, such as the Obsidian Google Calendar plugin, or an external script that reads your vault files and pushes the metadata via the Google Calendar API.

Does this method work on the Obsidian mobile app?

Yes, DataviewJS queries execute successfully on both iOS and Android versions of Obsidian. Ensure that your queries are scoped to specific folders (using dv.pages('"folder-name"')) rather than querying the entire vault to ensure the mobile app maintains smooth performance.

What happens if I miss a recurring task for several days?

The DataviewJS script calculates the differential between the stated due date and today. If a task was due three days ago, the script will output a negative day count. By wrapping this in conditional logic, you can easily label the task as ”🔴 Overdue” and sort it to the absolute top of your dashboard.