Essential knowledge for developing Logseq plugins for DB (database) graphs...
Comprehensive guidance for building Logseq plugins for DB (database) graphs, organized into three layers: authoritative upstream documentation, production-tested patterns, and related sibling skills.
This skill provides essential knowledge for building Logseq plugins that work with the new DB graph architecture. It covers:
Use this skill when developing Logseq plugins that:
| Aspect | Markdown Graphs | DB Graphs |
|---|---|---|
| Data Storage | Files (.md) | Database (SQLite) |
| Properties | YAML frontmatter | Typed database entities |
| Tags | Simple text markers | Classes with schemas |
| Queries | File-based attributes | Datalog / Database relationships |
| Property Access | Text parsing | Namespaced keys (:user.property/name) |
Precedence: Layer 1 is authoritative ground truth for API contracts. Layer 2 adds production-validated context and patterns not covered by official docs. When they conflict, Layer 1 wins on API facts; Layer 2 wins on real-world pitfalls (things that work on paper but fail in practice).
Source: mirrored verbatim from logseq/logseq libs/development-notes/ via scripts/sync-logseq-docs.sh. Each file carries a footer recording upstream commit SHA and fetch timestamp. License: AGPL-3.0 (see references/logseq-official/LICENSE).
| File | Covers |
|---|---|
references/logseq-official/AGENTS.md |
AI-agent development guide ā SDK repo structure, core patterns, conventions |
references/logseq-official/starter_guide.md |
Plugin setup walkthrough: Node/TypeScript install, Logseq dev environment, hello world |
references/logseq-official/db_properties_skill.md |
DB properties SDK reference ā schema definition, tags-as-classes, property operations |
references/logseq-official/db_properties_guide.md |
File graph vs DB graph properties ā text vs typed entities, SDK API differences |
references/logseq-official/db_query_guide.md |
Datascript query guide ā logseq.DB.q, datascriptQuery, parameterized Datalog |
references/logseq-official/db_tag_property_idents_notes.md |
Ident system ā namespace conventions (:logseq.property/, :plugin.property.*), when idents apply |
references/logseq-official/experiments_api_guide.md |
logseq.Experiments ā React integration, custom renderers, script loading, ClojureScript interop |
Refresh: bash scripts/sync-logseq-docs.sh from repo root. Idempotent ā no-op if upstream HEAD matches .last-synced-sha.
Battle-tested code from real-world plugin development. All patterns validated through logseq-checklist v1.0.0.
Tag Detection ā Reliable multi-layered detection
Three-tier approach (content ā datascript ā properties) for maximum reliability when block.properties.tags fails.
Search for: hasTag, block.properties.tags undefined, multi-layered
Pitfalls & Solutions ā Errors and fixes discovered in production
Tag creation validation, property conflicts, query syntax mistakes, or-join variable mismatches, method-name errors.
Search for: validation errors, query returns no results, addTag not a function
Event Handling ā DB.onChanged patterns Database change detection, datom filtering, debouncing strategies. Essential for plugins that maintain derived state.
Search for: DB.onChanged, debouncing, transaction datoms
Property Management ā Reading property values Iteration patterns for unknown property names, type-based detection, namespaced key access.
Search for: property iteration, namespaced keys, :user.property/
Core APIs ā Essential methods Tag/class management, page/block creation, property operations, icons, utilities.
Search for: createTag, addBlockTag, upsertProperty, createPage
Queries and Database ā Datalog patterns
Query syntax, common patterns, caching strategies, tag inheritance with or-join, :block/title vs :block/name.
Search for: datascriptQuery, datalog, caching, or-join, tag inheritance
Plugin Architecture ā Best practices File organization, settings registration, error handling, testing strategy, deployment checklist.
Search for: file organization, settings schema, production patterns
For specialized concerns, defer to sibling skills with their own activation triggers:
| Skill | Use for |
|---|---|
logseq-schema (RCmerci) |
Authoritative Datascript schema reference when writing Datalog queries ā covers entity attributes, relationships, cardinality. Install from github.com/RCmerci/skills. |
logseq-electron-debug (RCmerci) |
Chrome DevTools against a running Logseq app ā useful when debugging your plugin's runtime behavior. Install from github.com/RCmerci/skills. |
logseq-db-knowledge |
Foundational DB graph concepts ā use alongside this skill for understanding why DB graphs work the way they do. |
logseq-cli-skill |
Logseq CLI usage ā Datalog queries run from shell, useful for bulk operations outside plugins. |
mkdir my-logseq-plugin
cd my-logseq-plugin
pnpm init
pnpm add @logseq/libs
pnpm add -D typescript vite vite-plugin-logseq @types/node
mkdir src
src/index.ts ā Entry point:
import '@logseq/libs'
async function main() {
console.log('Plugin loaded')
// Register settings, initialize features
}
logseq.ready(main).catch(console.error)
vite.config.ts:
import { defineConfig } from 'vite'
import logseqDevPlugin from 'vite-plugin-logseq'
export default defineConfig({
plugins: [logseqDevPlugin()],
build: { target: 'esnext', minify: 'esbuild', sourcemap: true }
})
package.json:
{
"name": "my-logseq-plugin",
"version": "0.0.1",
"main": "dist/index.js",
"scripts": { "build": "vite build", "dev": "vite build --watch" },
"logseq": { "id": "my-logseq-plugin", "title": "My Logseq Plugin", "main": "dist/index.html" }
}
pnpm run dev # Watch mode
pnpm run build # Production build
# Load plugin: Settings ā Plugins ā Load unpacked plugin
Properties in DB graphs are stored as namespaced keys on block objects:
const block = await logseq.Editor.getBlock(uuid)
// Direct access
const value = block[':user.property/myProperty']
// Iteration (if name unknown)
for (const [key, value] of Object.entries(block)) {
if (key.startsWith(':user.property/')) { /* ... */ }
}
CRITICAL: block.properties.tags and block.properties[name] are often unreliable. Use direct key access or iteration instead.
Simple property checks fail. Use multi-layered detection ā see references/tag-detection.md for the full pattern.
// Tier 1: Content check (fast)
if (block.content.includes('#mytag')) return true
// Tier 2: Datascript query (reliable)
const results = await logseq.DB.datascriptQuery(
`[:find (pull ?b [*]) :where [?b :block/tags ?t] [?t :block/title "mytag"]]`
)
// Tier 3: Properties fallback (rarely works)
if (block.properties?.tags?.includes('mytag')) return true
For plugins that maintain derived state:
if (logseq.DB?.onChanged) {
logseq.DB.onChanged((changeData) => {
const { txData } = changeData
for (const [entityId, attribute, value, txId, added] of txData) {
if (attribute.includes('property')) scheduleUpdate(entityId)
}
})
}
See references/event-handling.md for debouncing strategies.
Always define property types before using them:
await logseq.Editor.upsertProperty('title', { type: 'string' })
await logseq.Editor.upsertProperty('year', { type: 'number' })
await logseq.Editor.upsertProperty('published', { type: 'checkbox' })
await logseq.Editor.upsertProperty('modifiedAt', { type: 'datetime' })
await logseq.Editor.createPage('Item', {
title: 'My Item',
year: 2024,
published: true,
modifiedAt: Date.now()
})
// 1. Create tag
const tag = await logseq.Editor.createTag('zot')
// 2. Define properties FIRST
await logseq.Editor.upsertProperty('title', { type: 'string' })
await logseq.Editor.upsertProperty('author', { type: 'string' })
await logseq.Editor.upsertProperty('year', { type: 'number' })
// 3. Add properties to tag schema (parent frame API)
const parentLogseq = (window as any).parent?.logseq
await parentLogseq.api.add_tag_property(tag.uuid, 'title')
await parentLogseq.api.add_tag_property(tag.uuid, 'author')
await parentLogseq.api.add_tag_property(tag.uuid, 'year')
// 4. Create page with tag and properties
await logseq.Editor.createPage('My Item', {
tags: ['zot'],
title: 'Paper Title',
author: 'Jane Doe',
year: 2024
})
const query = `
{:query [:find (pull ?b [*])
:where
[?b :block/tags ?t]
[?t :block/title "zot"]]}
`
const results = await logseq.DB.datascriptQuery(query)
Tag Hierarchies (items tagged with #task OR any tag extending #task):
const query = `
{:query [:find (pull ?b [*])
:where
(or-join [?b]
(and [?b :block/tags ?t]
[?t :block/title "task"])
(and [?b :block/tags ?child]
[?child :logseq.property.class/extends ?parent]
[?parent :block/title "task"]))]}
`
See references/queries-and-database.md for advanced patterns.
const pendingUpdates = new Set<string>()
let updateTimer: NodeJS.Timeout | null = null
function handleDatabaseChanges(changeData: any): void {
const txData = changeData?.txData || []
for (const [entityId, attribute, value, txId, added] of txData) {
if (attribute.includes('property')) {
pendingUpdates.add(String(entityId))
if (updateTimer) clearTimeout(updateTimer)
updateTimer = setTimeout(async () => {
for (const id of pendingUpdates) await updateBlock(id)
pendingUpdates.clear()
}, 300)
}
}
}
File Structure:
src/
āāā index.ts # Entry point, initialization
āāā events.ts # DB.onChanged handlers, debouncing
āāā logic.ts # Pure business logic (testable)
āāā settings.ts # Settings schema and accessors
āāā types.ts # TypeScript interfaces
Settings Registration:
import { SettingSchemaDesc } from '@logseq/libs/dist/LSPlugin.user'
const settings: SettingSchemaDesc[] = [
{
key: 'tagName',
type: 'string',
title: 'Tag Name',
description: 'Tag to monitor',
default: 'mytag'
}
]
logseq.useSettingsSchema(settings)
See references/plugin-architecture.md for error handling, testing, and deployment.
addBlockTag() not addTag()block.properties.tags ā iterate namespaced keys:block/title not :db/ident for custom tagscreated, modified ā use dateAdded, dateModifiedYYYY-MM-DD for date propertiesgetPage()See references/pitfalls-and-solutions.md for detailed solutions.
When encountering issues:
logseq-electron-debug skill (RCmerci) ā for debugging Logseq itselfThree layers, in order of priority:
Load the files you need for the current task. Layer 1 answers "what does the API do"; Layer 2 answers "what breaks in practice"; Layer 3 answers adjacent concerns that deserve their own skill activation.