// DSL Query - Logseq's query language
const results = await logseq.DB.q('[[page-name]]')
const todos = await logseq.DB.q('(task TODO DOING)')
// Datascript Query - Datalog syntax (more powerful)
const results = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where [?b :block/marker "TODO"]]
`)
// Query with parameters
const results = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:in $ ?name
:where
[?p :block/name ?name]
[? b :block/page ?p]]
`, `"my-page"`)
// Watch database changes
logseq.DB.onChanged(({ blocks, txData }) => {
console.log('Changed blocks:', blocks)
})
// Watch specific block changes
logseq.DB.onBlockChanged(uuid, (block, txData) => {
console.log('Block changed:', block)
})
[: find <return-value> ;; what to return
:in $ <params... > ;; input parameters ($ = database)
:where <clauses...>] ;; query conditions
;; Return entity IDs
[:find ?b : where ...]
;; Return multiple values
[:find ?b ? name :where ...]
;; Return full entity (commonly used)
[:find (pull ?b [*]) :where ...]
;; Return specific attributes
[:find (pull ?b [: block/uuid :block/content]) :where ...]
;; Aggregate functions
[:find (count ? b) :where ...]
[:find (min ? d) (max ?d) :where ...]
;; Basic triple: [entity attribute value]
[?b :block/marker "TODO"] ;; ? b's marker equals "TODO"
[? b :block/page ?p] ;; ?b belongs to page ? p
[?p :block/name ?name] ;; ? p's name binds to ?name
;; Multiple conditions (implicit AND)
[?b :block/marker "TODO"]
[?b : block/page ?p]
[?p :block/journal? true] ;; all three must be satisfied
;; $ is the database (required)
[:find (pull ?b [*])
:in $
:where ...]
;; Single parameter
[:find (pull ?b [*])
:in $ ?marker
:where [?b :block/marker ? marker]]
;; Multiple parameters
[:find (pull ?b [*])
:in $ ?marker ? page-name
:where
[?b :block/marker ?marker]
[? p :block/name ?page-name]
[?b : block/page ?p]]
;; Collection parameter (match multiple values)
[:find (pull ?b [*])
:in $ [?marker ...]
:where [?b :block/marker ?marker]]
| Attribute | File Graph | DB Graph | Type | Description |
|---|---|---|---|---|
:block/uuid |
✅ | ✅ | UUID | Unique block identifier |
:block/content |
✅ | ❌ | String | Raw block content (File Graph only) |
:block/title |
❌ | ✅ | String | Block title/content (DB Graph only) |
:block/page |
✅ | ✅ | Ref | Parent page reference |
:block/parent |
✅ | ✅ | Ref | Parent block reference |
:block/left |
✅ | ❌ | Ref | Left sibling block |
:block/order |
❌ | ✅ | String | Block order in DB Graph |
:block/refs |
✅ | ✅ | Ref[] | Referenced pages/blocks |
:block/marker |
✅ | ❌ | String | Task marker (TODO/DOING/DONE) |
:block/priority |
✅ | ❌ | String | Priority (A/B/C) |
:block/scheduled |
✅ | ❌ | Int | Scheduled date (YYYYMMDD) |
:block/deadline |
✅ | ❌ | Int | Deadline date (YYYYMMDD) |
:block/properties |
✅ | ❌ | Map | Properties as key-value map |
:block/tags |
✅ | ✅ | Ref[] | Tag references |
:block/link |
❌ | ✅ | Ref | Link to class/tag in DB Graph |
:block/tx-id |
❌ | ✅ | Int | Transaction ID |
:block/created-at |
✅ | ❌ | Int | Creation timestamp (File Graph) |
:block/updated-at |
✅ | ❌ | Int | Update timestamp (File Graph) |
| Attribute | File Graph | DB Graph | Type | Description |
|---|---|---|---|---|
:block/name |
✅ | ✅ | String | Page name (lowercase) |
:block/original-name |
✅ | ✅ | String | Original page name |
:block/journal? |
✅ | ✅ | Boolean | Is journal page |
:block/journal-day |
✅ | ✅ | Int | Journal date (YYYYMMDD) |
:block/type |
❌ | ✅ | String | Type ("page", "class", "property", etc.) |
:block/format |
✅ | ❌ | Keyword | Format (:markdown or :org) |
:block/file |
✅ | ❌ | Ref | Associated file reference |
| Attribute | Type | Description |
|---|---|---|
:logseq.property/created-at |
Int (ms) | Creation timestamp |
:logseq.property/updated-at |
Int (ms) | Update timestamp |
:logseq.property/icon |
Object | Icon definition {type, id} |
:logseq.property/hide-empty-value |
Boolean | Hide property if empty |
:logseq.property/closed-value-mode |
Boolean | Enum mode for property |
:logseq.property/closed-values |
Array | Allowed enum values |
:logseq.property/schema |
Object | Property schema definition |
:logseq.property. class/extends |
Ref[] | Parent class references |
:logseq.property.class/properties |
Ref[] | Class property references |
In DB Graph, tasks use a different system based on tags/classes:
| Attribute | Description |
|---|---|
:block/tags |
Reference to task status class (e.g., #logseq.class/Todo) |
:logseq.task/status |
Task status property |
:logseq.task/priority |
Task priority property |
:logseq.task/deadline |
Task deadline property |
:logseq.task/scheduled |
Task scheduled property |
File Graph:
// All TODOs
const todos = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where [?b :block/marker "TODO"]]
`)
// TODO or DOING
const activeTasks = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:in $ [?m ...]
:where [?b :block/marker ? m]]
`, `["TODO" "DOING"]`)
// Tasks with priority A
const priorityTasks = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where
[?b :block/marker ? m]
[?b : block/priority "A"]]
`)
// Scheduled for today
const today = new Date().toISOString().slice(0, 10).replace(/-/g, '')
const scheduled = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:in $ ? today
:where [?b : block/scheduled ?today]]
`, parseInt(today))
DB Graph:
// All tasks with TODO status
const todos = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where
[?b :block/tags ?tag]
[?tag :db/ident : logseq.class/Todo]]
`)
// Tasks by status property
const tasks = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where
[?b :logseq.task/status ?s]
[?s :db/ident :logseq. task/status. todo]]
`)
Both File Graph and DB Graph:
// All journal pages
const journals = await logseq.DB.datascriptQuery(`
[:find (pull ?p [*])
:where [?p :block/journal? true]]
`)
// Journals in specific month
const monthJournals = await logseq.DB.datascriptQuery(`
[:find (pull ?p [*])
:where
[?p :block/journal? true]
[?p : block/journal-day ?d]
[(>= ?d 20250101)]
[(<= ?d 20250131)]]
`)
// Blocks in a specific page
const blocks = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:in $ ? name
:where
[? p :block/name ?name]
[?b :block/page ?p]]
`, `"my-page"`)
DB Graph only:
// Pages by type
const pages = await logseq.DB.datascriptQuery(`
[:find (pull ?p [*])
:where [?p :block/type "page"]]
`)
// Class/Tag pages
const classes = await logseq.DB.datascriptQuery(`
[:find (pull ?p [*])
:where [?p : block/type "class"]]
`)
// Recently created pages (last 7 days)
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000
const recentPages = await logseq.DB.datascriptQuery(`
[:find (pull ?p [*])
:where
[?p :block/type "page"]
[?p :logseq.property/created-at ?created]
[(> ? created ${sevenDaysAgo})]]
`)
Both File Graph and DB Graph:
// Blocks referencing a specific page
const refs = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:in $ ?page
:where
[? p :block/name ?page]
[?b :block/refs ?p]]
`, `"target-page"`)
// Block references (backlinks)
const blockRefs = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:in $ ?uuid
:where
[? target :block/uuid ?uuid]
[?b :block/refs ?target]]
`, `#uuid "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"`)
File Graph:
// Blocks with specific property
const withProp = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where
[?b :block/properties ?props]
[(get ?props :status)]]
`)
// Property equals specific value
const statusDone = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where
[?b :block/properties ?props]
[(get ?props :status) ?v]
[(= ?v "done")]]
`)
DB Graph:
// Blocks with property value (properties are direct attributes)
const withProp = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where
[?b : user. property/status ?v]]
`)
// Query by property ident
const results = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where
[? prop :db/ident : user.property/status]
[?b ? prop ? v]
[?v :block/title "done"]]
`)
File Graph:
// Content contains keyword
const results = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where
[? b :block/content ?c]
[(clojure.string/includes? ?c "keyword")]]
`)
DB Graph:
// Title contains keyword
const results = await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where
[?b :block/title ?t]
[(clojure.string/includes? ?t "keyword")]]
`)
;; Comparison
[(> ? d 20250101)]
[(>= ?d 20250101)]
[(< ?d 20250131)]
[(<= ?d 20250131)]
[(= ?v "value")]
[(not= ?v "value")]
;; String operations
[(clojure.string/includes? ? s "text")]
[(clojure.string/starts-with? ?s "prefix")]
[(clojure.string/ends-with? ?s "suffix")]
[(clojure.string/blank? ?s)]
;; Collection operations
[(contains? #{"A" "B" "C"} ?v)]
;; Null checks
[(some? ?v)]
[(nil? ?v)]
;; Math operations
[(+ ?a ?b) ? sum]
[(- ?a ?b) ?diff]
[(* ?a ?b) ?product]
;; NOT - exclude conditions
[: find (pull ?b [*])
:where
[? b :block/marker ? m]
(not [?b :block/priority "C"])]
;; OR - any condition matches
[:find (pull ?b [*])
:where
(or [?b :block/marker "TODO"]
[?b : block/marker "DOING"])]
;; OR with AND (or-join)
[:find (pull ?b [*])
:where
(or-join [? b]
(and [?b :block/marker "TODO"]
[?b :block/priority "A"])
(and [?b :block/marker "DOING"]))]
// Count
const count = await logseq.DB.datascriptQuery(`
[:find (count ?b)
:where [?b : block/marker "TODO"]]
`)
// Group by page
const grouped = await logseq.DB.datascriptQuery(`
[:find ? name (count ?b)
:where
[?b :block/marker "TODO"]
[?b :block/page ?p]
[?p :block/name ?name]]
`)
// Min/Max
const range = await logseq.DB.datascriptQuery(`
[:find (min ?d) (max ?d)
:where
[?p :block/journal? true]
[?p :block/journal-day ?d]]
`)
import '@logseq/libs'
async function detectGraphType(): Promise<'db' | 'file'> {
const result = await logseq.DB.datascriptQuery(`
[:find ? type .
:where [_ :block/type ?type]]
`)
return result ? 'db' : 'file'
}
async function queryTodos(graphType: 'db' | 'file') {
if (graphType === 'file') {
return await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where [?b :block/marker "TODO"]]
`)
} else {
return await logseq.DB.datascriptQuery(`
[:find (pull ?b [*])
:where
[?b :block/tags ?tag]
[?tag :db/ident :logseq.class/Todo]]
`)
}
}
async function queryBlockContent(uuid: string, graphType: 'db' | 'file') {
const attr = graphType === 'file' ? ': block/content' : ':block/title'
return await logseq.DB.datascriptQuery(`
[:find ? content .
:in $ ?uuid
:where
[? b : block/uuid ?uuid]
[?b ${attr} ?content]]
`, `#uuid "${uuid}"`)
}
async function main() {
const graphType = await detectGraphType()
console.log('Graph type:', graphType)
logseq.Editor.registerSlashCommand('Query TODOs', async () => {
try {
const results = await queryTodos(graphType)
const todos = results?.flat() || []
if (todos.length) {
const content = todos.map(t => `- ((${t.uuid}))`).join('\n')
await logseq.Editor.insertAtEditingCursor(content)
} else {
await logseq.UI.showMsg('No TODOs found', 'warning')
}
} catch (e) {
console.error('Query failed:', e)
await logseq.UI.showMsg('Query failed', 'error')
}
})
}
logseq.ready().then(main)
:block/name stores lowercase names; convert before querying"value"123#uuid "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx":keyword["a" "b" "c"].flat() processingpull with specific attributes instead of [*] when possible