↖ Writing

Using Dataview on Obsidian Publish

Obsidian Publish sites with a "recent new notes" and a "recently edited" section

On the homepage of my Obsidian Publish page, I have a section called "Recent Notes". But often I would create a note, and forget to update the list. At some point, I even got a message asking if the site was still active, since the Recent Notes section didn't seem to be changing. 😬

When our ability to remember a repetitive job fails, automation comes in!

Dataview Plugin on Obsidian Publish

To get recently edited notes in my local vault, I would use an Obsidian Dataview query like this:

TABLE WITHOUT ID
file.link AS Note, dateformat(file.mtime, "ff") AS Modified
FROM ""
WHERE publish
SORT file.mtime desc
LIMIT 7

This works well locally:

Split window of Obsidian with source mode dataview query on the left and the dynamic output on the right

But on Publish it just renders a code block:

Obsidian Publish site where the Dataview query code is displayed in a code block

This is because the Dataview Plugin is not "installed" on Obsidian Publish. Publish sites can only render Markdown files; they do not support dynamic queries.

Outputting Markdown from Dataview Queries

Luckily for us, Dataview can output the result of a query in plain Markdown. The dv.queryMarkdown method receives a query and returns Markdown:

await dv.queryMarkdown("LIST FROM #tag");
// => { successful: true, value: { "- [[Page 1]]\n- [[Page 2]]" } }

We can run this method in an Obsidian Templater template to write the Markdown output of a query to a file.

// Update Publish Files.md
<%*
const dv = app.plugins.plugins["dataview"].api;
const filename = "Recently Edited";
const query = `TABLE WITHOUT ID
file.link AS Note, dateformat(file.mtime, "ff") AS Modified
FROM ""
WHERE publish SORT file.mtime desc
LIMIT 7`;

const tFile = tp.file.find_tfile(filename);
const queryOutput = await dv.queryMarkdown(query);

// write query output to file
await app.vault.modify(tFile, queryOutput.value);
%>

Whenever this template is run, the Markdown result of the TABLE… Dataview query is written to the file "Recently Edited".

Split window of Obsidian where a static Markdown table is displayed on the left in source mode and the same table is shown on the right in preview mode.Notice how Source Mode on the left displays a standard Markdown table.

Important: This code completely overwrites the target note.

Writing to multiple files

We can rewrite the code to support multiple filenames and queries:

// Update Publish Files.md
<%*
const dv = app.plugins.plugins["dataview"].api;
const openPublishPanel = app.commands.commands["publish:view-changes"].callback;

const fileAndQuery = new Map([
  [
    "Recently edited",
    'TABLE WITHOUT ID file.link AS Note, dateformat(file.mtime, "ff") AS Modified FROM "50 Slipbox" OR "30 External" WHERE publish SORT file.mtime desc LIMIT 7',
  ],
  [
    "Recent new files",
    'TABLE WITHOUT ID file.link AS Note, dateformat(file.ctime, "DD") AS Added FROM "50 Slipbox" OR "30 External" WHERE publish SORT file.ctime desc LIMIT 7',
  ],
]);

await fileAndQuery.forEach(async (query, filename) => {

  const tFile = tp.file.find_tfile(filename);
  const queryOutput = await dv.queryMarkdown(query);

  // write query output to file
  await app.vault.modify(tFile, queryOutput.value);

});
%>

When we run this template, the query outputs are written into the files Recently edited and Recent new files. You can add as many queries and filenames as you'd like!

I wanted to try out the new-ish Map() syntax, but an array of objects would have worked as well! If you're unfamiliar with Maps in JavaScript, it is worth to read more on MDN.

Now we have a template that writes the Markdown output of multiple queries into corresponding files. But at this point, we still need to remember to run this template before uploading changes to Obsidian Publish.

Final template

To upload changes to Obsidian Publish, we would usually open the Command Palette and then run the Publish: Publish changes… command.

But we can move this step into the "Update Publish Files" template, so that all of our Publish-related actions are in one place:

  1. The template first writes query results to the corresponding files,
  2. then opens the "Publish Changes" dialog

The "Update Publish Files" template now acts as a replacement for the Publish: Publish changes… command.

In the Templater Options, we can assign a hotkey to our "Update Publish Files" template. I had CMD + P bound to open the Publish panel. Now I assigned this hotkey to the template. I keep the muscle memory of publishing new notes, and every time the Dataview files are updated in the background.

Here is the final template, including some other improvements:

  1. Writes publish: true in the Frontmatter of the file and adds a comment,
  2. Creates a file if the filename doesn't exist,
  3. Includes error handling and progress notifications.
// Update Publish Files.md

<%*
const dv = app.plugins.plugins["dataview"].api;
const openPublishPanel = app.commands.commands["publish:view-changes"].callback;

// Add as many filenames and queries as you'd like!
const fileAndQuery = new Map([
  [
    "Recently edited",
    'TABLE WITHOUT ID file.link AS Note, dateformat(file.mtime, "ff") AS Modified FROM "50 Slipbox" OR "30 External" WHERE publish SORT file.mtime desc LIMIT 7',
  ],
  [
    "Recent new files",
    'TABLE WITHOUT ID file.link AS Note, dateformat(file.ctime, "DD") AS Added FROM "50 Slipbox" OR "30 External" WHERE publish SORT file.ctime desc LIMIT 7',
  ],
]);

await fileAndQuery.forEach(async (query, filename) => {
  if (!tp.file.find_tfile(filename)) {
    await tp.file.create_new("", filename);
    new Notice(`Created ${filename}.`);
  }
  const tFile = tp.file.find_tfile(filename);
  const queryOutput = await dv.queryMarkdown(query);
  const fileContent = `---\npublish: true\n---\n%% update via "Update Publish Files" template %% \n\n${queryOutput.value}`;
  try {
    await app.vault.modify(tFile, fileContent);
    new Notice(`Updated ${tFile.basename}.`);
  } catch (error) {
    new Notice("⚠️ ERROR updating! Check console. Skipped file: " + filename , 0);
  }
});
openPublishPanel();
%>

Result

On my Publish site, I have added five Dataview-powered query notes so far: Recently edited notes and recently created ones, books that I am currently reading and that I read recently, as well as a list of 80+ books that I read.

Obsidian Publish site showing a table of books with three columns: Cover (showing the thumbnail), title and type (eg. non-fiction or fiction)Dataview queries can be as complex as you like, even including images and tags.

I embedded a few of those notes into the homepage of my Publish page. Whenever I publish new notes with the template, those files are automatically updated.

Obsidian Publish site with the sections "Recent new notes" and "Recently edited"  and a table of notes underneath each

Now when a reader stumbles upon the page, they immediately see the most recent notes — with minimal upkeep on my end!