31 Jan 2025

Advanced Logseq queries can be useful

Logseq has a nifty feature where if you use their time tracking primitives it’ll automatically use a query under the hood to show you the tasks that you’re currently working on (ie blocks that begin with NOW). I vaguely knew that under the hood it was using a Logseq advanced query, but hadn’t previously looked into how they actually worked, so today I decided to rectify that.

I learned some interesting things while investigating this:

  • Every graph has a logseq/config.edn in it’s top-level folder

  • this config.edn file contains has a key called :default-queries which is what made the NOW feature work: it’s just a query that runs when you’re on a daily journal page

Here’s what the default queries look like in a freshly created Logseq graph:

:default-queries
 {:journals
  [{:title "🔨 NOW"
    :query [:find (pull ?h [*])
            :in $ ?start ?today
            :where
            [?h :block/marker ?marker]
            [(contains? #{"NOW" "DOING"} ?marker)]
            [?h :block/page ?p]
            [?p :block/journal? true]
            [?p :block/journal-day ?d]
            [(>= ?d ?start)]
            [(<= ?d ?today)]]
    :inputs [:14d :today]
    :result-transform (fn [result]
                        (sort-by (fn [h]
                                   (get h :block/priority "Z")) result))
    :collapsed? false}
   {:title "📅 NEXT"
    :query [:find (pull ?h [*])
            :in $ ?start ?next
            :where
            [?h :block/marker ?marker]
            [(contains? #{"NOW" "LATER" "TODO"} ?marker)]
            [?h :block/page ?p]
            [?p :block/journal? true]
            [?p :block/journal-day ?d]
            [(> ?d ?start)]
            [(< ?d ?next)]]
    :inputs [:today :7d-after]
    :collapsed? false}]}

These queries are using Datalog (a query language that’s popular with the clojure community). Unfortunately Logseq’s documentation on using them effectively is a little scattered (though this, and this got me pointed in the right direction) and didn’t walk through the basics.

I wanted to set up a query that would let me all the tasks I’d completed in the last 7 days. So with some squinting and a little help from my friendly neighborhood LLM I managed to come up with this query which looked right:

{:title "✅ DONE (Last 7 Days)"
 :query [:find (pull ?h [*])
         :in $ ?start ?today
         :where
         [?h :block/marker "DONE"]
         [?h :block/page ?p]
         [?p :block/journal? true]
         [?p :block/journal-day ?d]
         [(>= ?d ?start)]
         [(<= ?d ?today)]]
 :inputs [:7d :today]
 :collapsed? false}

Thing is, I didn’t want this query to show on all journal pages the way the default queries do, I wanted to have it live in it’s own page that I could go look at on-demand.

This was harder than it should’ve been to glean from the docs, but turns out to start a query you need to create a block (not a fenced code block, just a normal block) and put #+BEGIN_QUERY and #+END_QUERY at the end of it, and then put your query in the middle of it, after which Logseq will automagically render the query results similar to how the built-in NOW query that got me started on this whole thing works.