Building Logseq Plugins
When to Use This Skill
This skill auto-invokes when:
-
User wants to create a Logseq plugin
-
Questions about the Logseq Plugin API
-
Working with logseq.Editor, logseq.DB, logseq.App namespaces
-
Slash command registration
-
Plugin settings schema definition
-
DB vs MD version compatibility for plugins
-
User mentions "logseq plugin", "logseq extension", "@logseq/libs"
You are an expert in Logseq plugin development, with special focus on DB-version compatibility.
Plugin Architecture Overview
Logseq plugins run in sandboxed iframes and communicate with the main app via the Plugin API.
Plugin Structure
my-logseq-plugin/ ├── package.json # Plugin manifest ├── index.html # Entry point ├── index.js # Main logic (or TypeScript) ├── icon.png # Plugin icon (optional) └── README.md # Documentation
package.json Manifest
{ "name": "logseq-my-plugin", "version": "1.0.0", "main": "index.html", "logseq": { "id": "my-plugin-id", "title": "My Plugin", "icon": "./icon.png", "description": "Plugin description" } }
Plugin API Basics
Initialization
import '@logseq/libs'
async function main() { console.log('Plugin loaded')
// Register commands, settings, etc. logseq.Editor.registerSlashCommand('My Command', async () => { // Command logic }) }
logseq.ready(main).catch(console.error)
Key API Namespaces
Namespace Purpose
logseq.Editor
Block/page manipulation
logseq.DB
Database queries
logseq.App
App-level operations
logseq.UI
UI components
logseq.Settings
Plugin settings
DB-Version Specific APIs
Working with Properties
// Get block with properties (DB version) const block = await logseq.Editor.getBlock(blockUuid, { includeChildren: true })
// Properties are structured differently in DB version // Access via block.properties object const author = block.properties?.author
// Set property value await logseq.Editor.upsertBlockProperty(blockUuid, 'author', 'John Doe')
Working with Classes/Tags
// Get blocks with a specific tag/class
const books = await logseq.DB.datascriptQuery( [:find (pull ?b [*]) :where [?b :block/tags ?t] [?t :block/title "Book"]])
// Add tag to block await logseq.Editor.upsertBlockProperty(blockUuid, 'tags', ['Book', 'Fiction'])
Enhanced Block Properties APIs (2024+)
// New APIs for DB version await logseq.Editor.getBlockProperties(blockUuid) await logseq.Editor.setBlockProperties(blockUuid, { author: 'Jane Doe', rating: 5, published: '2024-01-15' })
Datalog Queries in Plugins
Basic Query
const results = await logseq.DB.datascriptQuery( [:find ?title :where [?p :block/title ?title] [?p :block/tags ?t] [?t :db/ident :logseq.class/Page]])
Query with Parameters
const results = await logseq.DB.datascriptQuery( [:find (pull ?b [*]) :in $ ?tag-name :where [?b :block/tags ?t] [?t :block/title ?tag-name]], ['Book'])
DB vs MD Query Differences
// MD version - file-based queries
const mdQuery = [:find ?content :where [?b :block/content ?content] [?b :block/page ?p] [?p :block/name "my-page"]]
// DB version - entity-based queries
const dbQuery = [:find ?title :where [?b :block/title ?title] [?b :block/tags ?t] [?t :block/title "My Tag"]]
UI Components
Slash Commands
logseq.Editor.registerSlashCommand('Insert Book Template', async () => { await logseq.Editor.insertAtEditingCursor(`
- [[New Book]] #Book author:: rating:: status:: To Read `) })
Block Context Menu
logseq.Editor.registerBlockContextMenuItem('Convert to Task', async (e) => { const block = await logseq.Editor.getBlock(e.uuid) await logseq.Editor.upsertBlockProperty(e.uuid, 'tags', ['Task']) await logseq.Editor.upsertBlockProperty(e.uuid, 'status', 'Todo') })
Settings Schema
logseq.useSettingsSchema([ { key: 'apiKey', type: 'string', title: 'API Key', description: 'Your API key for the service', default: '' }, { key: 'enableFeature', type: 'boolean', title: 'Enable Feature', default: true }, { key: 'theme', type: 'enum', title: 'Theme', enumChoices: ['light', 'dark', 'auto'], default: 'auto' } ])
DB-Version Compatibility Checklist
When building plugins for DB version:
-
Use :block/title instead of :block/content for page names
-
Handle structured properties (not just strings)
-
Support typed property values (numbers, dates, checkboxes)
-
Use tag-based queries instead of namespace queries
-
Test with both DB and MD graphs if supporting both
-
Handle the unified node model (pages ≈ blocks)
-
Use new block properties APIs when available
Common Patterns
Feature Detection
async function isDBGraph() { // Check if current graph is DB-based const graph = await logseq.App.getCurrentGraph() return graph?.name?.includes('db-') || graph?.type === 'db' }
async function main() { const isDB = await isDBGraph() if (isDB) { // DB-specific initialization } else { // MD-specific initialization } }
Property Type Handling
function formatPropertyValue(value, type) {
switch (type) {
case 'date':
return new Date(value).toISOString().split('T')[0]
case 'number':
return Number(value)
case 'checkbox':
return Boolean(value)
case 'node':
return [[${value}]]
default:
return String(value)
}
}
Development Workflow
-
Setup: Clone logseq-plugin-template or start fresh
-
Develop: Use npm run dev for hot reload
-
Test: Load unpacked plugin in Logseq
-
Build: npm run build for production
-
Publish: Submit to Logseq Marketplace
Loading Development Plugin
Logseq > Settings > Advanced > Developer Mode: ON Logseq > Plugins > Load unpacked plugin > Select folder
Resources
-
Logseq Plugin SDK
-
Plugin API Reference
-
Plugin Examples