Sorting DataviewJS dv.pages() by groups

I thought for sure the answer here was going to fit what I am looking for but for some reason its still not working.

I have a folder I call annotations, full of atomic “quote” notes. I’m attempting to list all quotes related to a given reference (in this case a book). At the moment it is working perfectly, the only issue is the sorting of the groups.

At the moment I’m using the following query:

let annotations = dv.pages('"📦 Resources/💬 Annotations"')
    .where(p => {
        if (p && p.file && p.file.frontmatter && p.file.frontmatter.sources) {
            return p.file.frontmatter.sources.some(s => ( + "").contains(
        return false;
    .sort(p => {
        let matchingSource = p.file.frontmatter.sources.find(s => ( + "").contains(;
        return matchingSource.location, "asc";
    .groupBy(p => {
        // Group by the location based on the match from the WHERE clause
        let matchingSource = p.file.frontmatter.sources.find(s => ( + "").contains(;
        return matchingSource ? matchingSource.location : "";

This works great and accounts for the fact that a single quote could have more than one source/reference, here’s an example: (more details can be found here)

id: A-202207031548
type: quote
quote: Men are born soft and supple; dead they are stiff and hard. Plants are born tender and pliant; dead, they are brittle and dry. Thus whoever is stiff and inflexible is a disciple of death. Whoever is soft and yielding is a disciple of life. The hard and stiff will be broken. The soft and supple will prevail.
- { person: [[Lao Tzu]], place: [[📕 The Art of War]], location: Chapter 20 }
- { person: , place: [[📕 Atomic Habits]], location: Chapter 20 }
- wisdom
- discipline
- habits
`` `dataviewjs 
`` `

Based on my results I’m iterating through the groups and rows with the following:

for (let annotation of annotations.values.sort(g => g.key, 'desc')){
    // console.log(annotation);
    if (annotation.key){
        dv.el('h3',annotation.key, { cls: "annotation-header"});
    } else {
        dv.el('h3',"Unknown", { cls: "annotation-header"});

    for (let row of annotation.rows){
        // console.log("row: "+ row.file.frontmatter.source);
        let svg_markup = '<svg xmlns="" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>';
        let link = document.createElement("a");
        link.href = "obsidian://open?vault=PKM&file=" + row.file.path.replace(" ", "%20");
        link.innerHTML = svg_markup;
        let a = link.outerHTML + '<span>'+row.file.frontmatter.quote+'</span>';
        dv.el("annotation", a);

My hope was to sort the headers by the location (Chapters), and then each row within those chapter headers by their ID would be great.

Unfortunately at the moment I’m getting the following results:

  • Chapter 10
  • Chapter 2
  • Chapter 9

Everything I’ve tried has made no adjustment to the order so I’m not sure what I’m doing wrong here. Any help I can get will go a long way, at this point I’ve worn out ChatGPT, the most reasonable answer I got was this:

let sortedAnnotations = annotations.sort((a, b) => {
  let keyA = a.key || "";
  let keyB = b.key || "";
  return keyA.localeCompare(keyB);

for (let annotation of sortedAnnotations) {
  if (annotation.key) {
    dv.el('h3', annotation.key, { cls: "annotation-header" });
  } else {
    dv.el('h3', "Unknown", { cls: "annotation-header" });

The problem with it appears to be that it compares the key of “A” and “B” which makes sense, but when I output those key values it appears “A” is an object and “B” is an integer which I believe is why they don’t result correctly.

Thanks in advance to any input you can provide