Lesson 5: How to Power Your Workflows Using Properties and Dynamic Variables

We’re on the home stretch! By now, you have all the knowledge to build increasingly complex note indexes and workflows.

In this lesson, we look at some of the other filter options in Logseq. Because not only can we use Boolean operators as filters and [[links]] as search terms, but it’s also possible to describe blocks and pages in other ways. Enter properties.

Properties are best thought of as metadata, or data about data. We can finely tune our search results by using properties in our queries.

For example, there’s a property that tells what page a block is on. Other properties tell us when a page was created or updated. But apart from these built-in properties, we can even create our own data structure using custom properties. Properties enable us to build powerful workflows, as we’ll see next week.

This lesson will be more like a reference guide than a step-by-step tutorial. That’s why there’s no exercise. Instead, look at what parts interest you and experiment with your own graph.

Next week, we’ll draw heavily from this guide and expand it based on your feedback. So, leave your questions and comments in this forum thread.

What are Logseq properties?

As per the official documentation, “properties are key-value pairs that allow you to annotate a block or page.”

If you’re unfamiliar with the term key-value pair, the easiest way to explain it is with a real-life example.

We humans have many different characteristics. The colors of our eyes, hair, and skin can all be characteristics that we can express in properties. My properties would be something like this in Logseq’s property syntax:

hair-color:: black
eye-color:: darkbrown
skin-color:: pink

The key part is a broad category, whereas the value is a subcategory or can even be a unique value.

The key is always followed by two colons (::), with no space between the key and the colons.

The value can be a single value or a list separated by commas:
key:: value 1, value 2, value 3

The key-value pairs above are an example of custom properties, which are always visible. But there is another category of properties, namely the ones that are hidden.

Hidden properties

Whenever you create a block or page in Logseq, several hidden properties (metadata) are also created. Information like when a block or page is created (created-at), when it’s updated (updated-at), what its parent block is (parent), and many other properties.

Visible properties

Visible properties are the ones we can edit. The built-in property we’ll use the most is tags, although alias can also come in handy when we want to save ourselves some typing.

We’ll cover the visible properties in-depth in the “Create your own properties” section of this lesson.

Now that we understand the basics of properties, let’s see some of the query filters we can use with them.

Time and date-specific filters

An important component of our knowledge work is time. In Logseq, time plays a crucial role with the Journals pages.

Logseq has several query filters that work with built-in properties. The most important date-specific filters are sort-by and between.


The sort-by filter lets us filter pages based on the hidden created-at and updated-at properties. It doesn’t actually filter results out; it only orders the results based on our chosen characteristic.

Currently, the sort-by only works when searching for pages, not blocks. The reason the created-at and updated-at properties are not added to individual blocks is due to performance. We might solve this in the future, but for now, keep in mind that sort-by only works with pages.

The syntax for the sort-by filter is as follows:

(sort-by key order)

First, we tell the Logseq query what filter we want to use, sort-by in this case. Then, we tell it what key we want to filter on (either created-at or updated-at). Finally, we give in the order we want to filter the values (desc or asc).

The sort order is by default set to descending (from newest to oldest). Because desc is the default value, you can leave it out from the filter if you want:

(sort-by created-at)

To show the results from oldest to newest, you have to set the sort order to asc (for ascending):

(sort-by created-at asc)

In proper Logseq syntax, this is what a sort-by query looks like:

{{query (and [[Search term]] (sort-by created-at asc)) }}


Apart from being a place to jot down our notes quickly, the journals page enables us to search our notes based on date.

The between filter allows us to look for blocks located on journal pages between two specific dates. The between filter supports a wide range of natural and logical language, which are basically dynamic variables (more on that at the end of this lesson).

The syntax for the between filter is as follows:

(between start end)

We can use natural language (English), plus and minus symbols, and numbers for the start and end dates. These are all the supported values, which can be used in combination if you wish:

  • now
  • today
  • yesterday
  • tomorrow
  • +
  • -
  • y (for years)
  • m (for months)
  • w (for weeks)
  • d (for days)
  • h (for hours)
  • min (for minutes)
  • any numbers
  • any date links

For example, this query looks for all blocks or branches that contain [[Search term]] and that are on a journals page between one week ago and now:

{{query (and [[Search term]] (between -7d now)) }}

Apart from relative numbers, you can also put in absolute dates like this:

{{query (and [[Search term]] (between [[Aug 19th, 2022]] [[Aug 26th, 2022]])) }}

(to see the full query, scroll to the right)

The flexibility of the between filter helps us with project planning and reviews. The dynamic variables allow us to write a query once and reuse it indefinitely.

Create your own properties

Apart from writing in an outline and adding links to individual blocks, we can create a more elaborate data structure in Logseq.

Using page tags and (page) properties, you can power your workflows without cluttering the text of your note with a bunch of links.

In this section, we see how to make a start with creating properties to power our queries. This is a big topic, so we’ll only scratch the surface. Next week we’ll use custom properties extensively as we build our workflows.


The built-in tags property lets us add tags that apply to an entire page. To add a tags property, write the following in the first block of a page:


Whenever you place a property in the first block of a page, you make that property a page property. The other page property you’ll see a lot is title, which gets automatically added to a page when you create it. To ensure a property you add is a page property, add it in the same block where you see the title:: key.

The tags property is a special type of page property as it’s built-in, so it has its own filter:

{{query (page-tags Philosophy) }}

The example above looks for all pages that contain tags:: Philosophy in the first block.


While the tags property helps to categorize pages, you sometimes want to add more unique characteristics to a page. This is where custom page properties come in handy.

A page property is any key-value pair that’s not built-in, and that’s placed in the first block of a page. The syntax is the same as the built-in properties:

custom-property:: value

The page-property filter allows us to search for custom properties and any specific value we’d like. The syntax is as follows:

(page-property key value)

For example, let’s say we use Logseq to write articles and add an article-status:: property to any article page. The possible values are idea, outline, draft, edit, and published.

To create an index of all articles in status:: idea, we write the following query:

{{query (page-property status idea) }}

To get an overview of all articles in status draft, we’d write this query:

{{query (page-property status draft) }}

But what if you primarily work with blocks and branches instead of pages? That’s where block properties come in.


A block property is much like a page property. But instead of adding the property to the first block of a page, block properties are within blocks that contain other content (like notes). Block properties only apply to the block itself, not to its children.

To add a property to a block, place your cursor at the end of a block’s content and press Shift-Enter. This creates a new line inside the block. Next, you type the property as you would otherwise.

Here’s an example of a block property:

This is a block that contains content. On the new line below is a status property that provides metadata to this block.
status:: outline

The syntax to search for a custom property is as follows:

(property key value)

That’s basically the same as the page-property filter! The query would look like this:

{{query (property status idea) }}

Custom properties enable us to build workflows with Logseq. Next week we’ll use them extensively to build workflows to create and learn from content.

Dynamic variables

We end this lesson by taking a brief look at dynamic variables. In the time and date-specific filters section, you already saw the dynamic values you could use for dates. But a few more dynamic variables can come in handy, mainly when used outside of the between filter.

The dynamic variables we can use in templates also work in macros like queries. The syntax for dynamic variables is as follows:

<% variable %>

[Here are some examples of dynamic variables we can use in queries]:

  • <% today %>
  • <% yesterday %>
  • <% tomorrow %>
  • <% current page %>
  • <% Last Friday %>

What happens when you put a dynamic variable in a query? When Logseq sees a variable, it interprets it and turns it into a [[link]].

As an example, say we add this query on a page titled Philosophy:

{{query (and [[Notes]] <% current page %>) }}

Logseq will interpret this as:

{{query (and [[Notes]] [[Philosophy]]) }}

You can see that Logseq turns dynamic variables into links when you add one to a template. In that case, the variable will be turned into an actual link upon running the template.

Next: let’s build with queries!

That was a lot of information! You should be proud of yourself for getting so far.

For now, spend some time reviewing your notes and asking any questions in today’s forum thread. After the weekend, we kick off the build week with another live lecture. Click here to set a reminder for yourself on YouTube.


Thank you for the very easy to follow lessons, @Ramses. This has been very helpful, and has allowed me to see that I had been making some things more complicated than it needs to be.

I’ve also discovered, from @LShark 's issue from yesterday’s lesson, that I’m still not quite getting Boolean Logic and will be spending the weekend working with test data in order to practice what I’ve learned this week before trying to dive into next week’s projects.

Just want to say I very much appreciate your time and effort in putting these lessons together and for leading these sprints. They have been invaluable resources and I after having a better foundation on how Logseq works, I’m better able to structure my data more purposefully so that I can better retrieve the information later.



Agreed, very valuable and love the bite-sized learning like this, it makes it easier to go thru and learn…


@Ramses many of these examples are missing the closing parenthesis.

i believe that is incorrect but you may want to double-check. thank you!

1 Like

Wow, you just unraveled so much confusion i’ve had for the past two months with my own duct taped queries: property vs page-property. thank you so much! This was an info-packed lesson today and I felt the on-ramp acceleration.


Hello Ramses,
If you haven one property then the syntax is:
{{query (property type first)}}
What is the syntax if you have two properties?
Is it something like:
{{query (and (property type first) (property type second))}}
This is not working, because …

So sloppy from my side! Thank you for spotting it :pray:t3: Changing it now.

That is the correct syntax, but remember that and reduces the results because both values have to be present in a block or branch to show up.

This is a picture of my attempt. Please explain why the result is 0.

Because there is no block with this property:

example:: first, second

If you want to list blocks with “first” only and blocks with “second” only, you need to use OR, not AND. See the lesson on boolean operators.

[Edit] somehow I think the syntax we use in Boolean Algebra is less ambiguous in this case:


is the OR operator and that + gives the idea that the list increases.

In lesson 5 I read " any property you add to a block is applied to the block itself and all of its children .". I will think even harder about this and try to work out examples. At the moment, unfortunately, I don’t understand any of it. In any case, thank you for your response.

You’re right @DjeeAr, this query should work given the fact that both values are part of the same branch. Just to make sure, are you on the latest desktop version (0.8.2.)?

(:point_up_2: this is not correct; see replies for the reason why block properties aren’t applied to a branch)

How could they inherit properties? Properties are used to define thinghs like being collapsed by Logseq itself and its plugin.

I’m pretty sure that making blocks inheriting properties now would result in breaking a lot of stuff…

1 Like

Yes, I make sure to always be on the latest revision.
I would hate to see time spent on this inappropriately afterwards. Let it rest for a while. They are sought after examples. They have no immediate practical value.

@Ramses FYI my proposed syntax for inline properties could be also used to make child blocks inherit properties:

- [[type:: book]] [[author:: Oscar Wilde]]
  - [[title:: The Picture of Dorian Gray]]
  - [[title:: The Importance of Being Earnest]]

A query listing books would result in this:

Title Author
The Picture of Dorian Gray Oscar Wilde
The Importance of Being Earnest Oscar Wilde
1 Like

@DjeeAr, I checked with @tienson, and @alex0 is right: block properties are not applied to their entire branch, only to the block itself. According to Tienson, the two main reasons are:

  1. It slows down search and queries.
  2. It makes refactoring later harder. Currently, properties are not blocks, they are just scalar values like texts or numbers, which means they’re not yet first-class “blocks”; properties need better schema and grouping support to bring ontology to Logseq.

And @alex0, as for:

I’m not sure how that would work performance-wise, given Tienson’s explanation above. But I’m out of my depth when it comes to engineering :wink:


Also, in his Logseq Mastery course, Dario tells me in lesson 10.4
“Make sure to indent so that all links are inheritable.” So this is a somewhat more widespread misunderstanding.

The keyword is links. Links are inherited; block properties are not inherited.

If you want to apply a property value to an entire branch, make a [[link]] of the property value. In that case, the link is applied to the entire branch. However, the block property itself (key-value pair) only applies to the block it’s part of.

1 Like

I did an experiment. Block properties are not inherited while page properties are.

{{query (and (page-property SomePageProperty spp1) (property SomeBlockProperty sbp1)) }} only returns blocks on pages with spp1 and where the block has sbp1. Even if sbp1 is a link, child blocks are not returned.

To get block-level inheritance with linked properties this works:
{{query (and (page-property SomePageProperty spp1) [[sbp1]] ) }}

So I guess that if you want block inheritance it’s ok to use #xyz or #[[xyz]] in the parent block if you don’t want a property.

Incorrect understanding by Mage:
I’m also using “:property-pages/enabled? false” so that unless I specifically create a linked property like [[linked-property]] logseq doesn’t automatically create a page, and I can see no changes in the data returned by queries.
(Alex pointed out the pages are created for property names, not values.)

Am I missing something important about properties vs linked properties?

Thanks, Mage.

Correct. See my previous comment in this thread: