logseq-db-plugin-dev
About
This skill provides essential knowledge for developing Logseq plugins specifically for DB (database) graphs. It covers critical differences from markdown-based graphs and includes setup, debugging, and @logseq/libs v0.2.3 integration. Use it when creating or troubleshooting plugins that interact with Logseq's database graph system.
Quick Install
Claude Code
Recommended/plugin add https://github.com/kerim/logseq-db-plugin-dev-skillgit clone https://github.com/kerim/logseq-db-plugin-dev-skill.git ~/.claude/skills/logseq-db-plugin-devCopy and paste this command in Claude Code to install this skill
Documentation
Logseq DB Plugin Development Reference
Target: @logseq/libs v0.2.3 | Logseq 0.11.0+ (DB graphs)
This guide focuses on DB (database) graphs, which differ fundamentally from markdown/file-based graphs. Critical differences are highlighted throughout.
Environment Setup
Package.json
{
"name": "logseq-example-plugin",
"version": "1.0.0",
"main": "dist/index.js",
"logseq": {
"id": "logseq-example-plugin",
"title": "Example Plugin",
"icon": "./icon.svg"
},
"dependencies": {
"@logseq/libs": "^0.2.3"
},
"devDependencies": {
"typescript": "^5.0.0",
"vite": "^5.0.0",
"vite-plugin-logseq": "^1.1.2"
}
}
Basic Plugin Entry Point
import '@logseq/libs'
async function main() {
console.log('Plugin loaded')
logseq.Editor.registerSlashCommand('My Command', async () => {
// Command implementation
})
}
logseq.ready(main).catch(console.error)
Creating Pages
MD Graphs (File-based)
// Simple page creation
const page = await logseq.Editor.createPage(
'Page Name',
{
property1: 'value1',
property2: 'value2'
}
)
DB Graphs (Database)
Key Difference: DB graphs support explicit schema definitions and automatic property namespacing.
// Page with explicit schema (v0.2.3+)
const page = await logseq.Editor.createPage(
'Article Title',
{
title: 'My Article',
year: 2025,
verified: true,
url: 'https://example.com'
},
{
schema: {
title: { type: 'string' },
year: { type: 'number' },
verified: { type: 'checkbox' },
url: { type: 'url' }
},
redirect: false,
createFirstBlock: false
} as any // TypeScript defs not yet updated
)
Property Types:
string- Textnumber- Numberscheckbox- Boolean checkboxesurl- URLs (validated)date- Date valuesdatetime- Date with timepage- Page referencesnode- Block references
Important:
- Properties are namespaced as
:plugin.property.{plugin-id}/{property-name} - Use
as anyfor schema parameter (TypeScript definitions lag behind runtime API) - Schema parameter eliminates DataCloneError from v0.0.17
Creating Class/Tag Pages
MD Graphs
Not applicable - tags are just page references.
DB Graphs
Key Difference: DB graphs have dedicated class/tag pages, but plugin API cannot set class property schemas.
// Create a class page (v0.2.3+)
const tagPage = await logseq.Editor.createPage(
'research',
{},
{
class: true
} as any
)
IMPORTANT LIMITATION: The schema parameter on createPage() creates page-level schemas, NOT class schemas. Plugins cannot programmatically set the :logseq.property.class/properties field due to security restrictions.
Alternative: Property Entities with Schemas
Instead of class schemas, use property entities:
// Step 1: Create property entities with schemas
await logseq.Editor.upsertProperty('year', {
type: 'number'
})
await logseq.Editor.upsertProperty('title', {
type: 'string'
})
await logseq.Editor.upsertProperty('tags', {
type: 'string',
cardinality: 'many'
})
// Step 2: Use properties on pages with explicit schemas
const page = await logseq.Editor.createPage(
'Study on XYZ #research',
{}
)
// Set each property with its schema
await logseq.Editor.upsertBlockProperty(
page.uuid,
'year',
2025,
{ type: 'number' }
)
await logseq.Editor.upsertBlockProperty(
page.uuid,
'title',
'XYZ Study',
{ type: 'string' }
)
Why This Works:
- Property entities are created with schemas stored at the property level
upsertBlockProperty()with schema parameter sets typed values on pages- Properties maintain correct types without class schema inheritance
- Fully automatic - no manual setup required
What Cannot Be Done:
// ❌ FAILS - Plugins cannot set class/properties
await logseq.Editor.upsertBlockProperty(
classPage.uuid,
'class/properties',
['year', 'title', 'verified']
)
// Error: "Plugins can only upsert its own properties"
The :logseq.property.class/properties field is a system property that plugins cannot modify. Users must manually add properties to class schemas via the Logseq UI if they want centralized schema management.
Adding Tags to Pages
MD Graphs
// Tags can be added as page properties or in content
const page = await logseq.Editor.createPage(
'My Page',
{ tags: ['tag1', 'tag2'] }
)
DB Graphs
Key Difference: Tags must be included in the page title. No explicit addTag() API exists.
// ✅ CORRECT - Tags in page title
const page = await logseq.Editor.createPage(
'My Article #research #science',
{ year: 2025 }
)
// ❌ WRONG - No effect in DB graphs
const page = await logseq.Editor.createPage(
'My Article',
{ tags: ['research', 'science'] } // Creates custom property, not actual tags
)
// ❌ WRONG - addTag() method doesn't exist
await logseq.Editor.addTag(page.uuid, 'research') // Error: "Not existed method"
Creating Blocks
MD Graphs
// Simple block insertion
const block = await logseq.Editor.insertBlock(
parentBlock,
'Block content',
{ sibling: true }
)
DB Graphs
Key Difference: Blocks in DB graphs have strict UUID requirements and different property handling.
// Single block with properties
const block = await logseq.Editor.appendBlockInPage(
'Page Name',
'Block content',
{
properties: {
customProp: 'value'
}
}
)
// Batch blocks (nested structure)
const blocks = await logseq.Editor.insertBatchBlock(
parentBlock,
[
{
content: 'Parent block',
children: [
{ content: 'Child 1' },
{ content: 'Child 2' }
]
}
],
{ sibling: false }
)
Important:
propertiesparameter ininsertBatchBlockis not supported in DB graphs- Use
insertBlockwithpropertiesoption instead for individual blocks - Blocks without UUIDs get auto-generated UUIDs
Property Management
Creating Property Entities (v0.2.3+)
DB Graphs Only: Properties can be created as standalone entities with schemas.
// Create a property entity with schema
const property = await logseq.Editor.upsertProperty('customField', {
type: 'string',
cardinality: 'one'
})
// Property is created with namespace
// Result: :plugin.property.my-plugin-id/customfield
// Get property entity
const prop = await logseq.Editor.getProperty('customField')
console.log(prop.id) // Database ID
console.log(prop.type) // 'string'
Available Schema Options:
{
type: 'default' | 'number' | 'date' | 'datetime' | 'checkbox' | 'url' | 'node' | 'json' | 'string',
cardinality: 'one' | 'many', // Default: 'one'
hide?: boolean,
public?: boolean
}
Benefits:
- Properties get database IDs
- Schemas stored at property level
- Reusable across pages
- Type validation automatic
Setting Properties with Schemas
// Set a property on a block/page with schema
await logseq.Editor.upsertBlockProperty(
page.uuid,
'year',
2025,
{ type: 'number' }
)
// Multi-value property
await logseq.Editor.upsertBlockProperty(
page.uuid,
'tags',
['research', 'science'],
{ type: 'string', cardinality: 'many' }
)
When to use upsertBlockProperty() with schema:
- When you need explicit type control per-property
- When property entity doesn't exist yet
- When you want to ensure type even if property schema changes
When to use upsertProperty() first:
- When you have a set of standard properties
- When you want centralized schema definitions
- When you want to reuse schemas across many pages
Property Handling
Reserved Property Names
Both MD and DB: Avoid these reserved names - they have special validation:
createdmodifiedupdated
Issue in DB graphs (v0.2.3):
- Reserved names don't throw errors anymore
- BUT they silently drop all subsequent properties
- Workaround: Use alternative names
// ❌ PROBLEMATIC - Drops 'title' property
const page = await logseq.Editor.createPage(
'My Page',
{
created: '2025-01-15',
title: 'Will be dropped' // This gets lost
}
)
// ✅ SAFE - Use alternative names
const page = await logseq.Editor.createPage(
'My Page',
{
dateCreated: '2025-01-15',
publicationDate: '2025-01-15',
title: 'Works correctly'
}
)
Property Namespacing
MD Graphs: Properties stored as-is in frontmatter.
DB Graphs: Plugin properties are automatically namespaced.
// Plugin creates this property:
{ title: 'My Article' }
// Stored in DB as:
:plugin.property.my-plugin-id/title "My Article"
Important: This prevents conflicts but means you can't modify Logseq's built-in properties like :block/tags.
Querying Data
MD Graphs
Query markdown files directly or use Datalog on file-based database.
DB Graphs
Key Difference: Must use Datalog queries against the database.
// Find pages by property value
const results = await logseq.DB.q(`
[:find (pull ?b [*])
:where
[?b :plugin.property.my-plugin/doi "${doi}"]]
`)
// Check if page exists
const existing = await logseq.DB.q(`
[:find (pull ?b [*])
:where
[?b :block/original-name ?name]
[(= ?name "${pageName}")]]
`)
Important:
- Use
:block/original-namefor case-sensitive page name lookups - Property queries must use full namespaced property name
- Results are arrays of arrays:
[[{page1}], [{page2}]]
Handling HTML Content
MD Graphs
Can insert HTML directly into markdown.
DB Graphs
Key Difference: HTML must be converted to markdown first.
import TurndownService from 'turndown'
const turndownService = new TurndownService({
headingStyle: 'atx',
codeBlockStyle: 'fenced'
})
// Convert HTML to markdown
const markdown = turndownService.turndown(htmlContent)
const block = await logseq.Editor.appendBlockInPage(
pageName,
markdown
)
Duplicate Detection
MD Graphs
Check for existing files.
DB Graphs
Key Difference: Query by unique property, not page title.
// ❌ UNRELIABLE - Page titles can change
const existing = await logseq.Editor.getPage(pageName)
// ✅ RELIABLE - Query by unique property (DOI, URL, etc.)
const results = await logseq.DB.q(`
[:find (pull ?b [*])
:where
[?b :plugin.property.my-plugin/doi "${doi}"]]
`)
if (results && results.length > 0) {
console.log('Page already exists')
}
Testing in Browser Console
Plugins run in iframes. To test functions from browser console:
// In your plugin code - expose to parent window
if (typeof parent !== 'undefined' && parent.window) {
// @ts-ignore
parent.window.myTestFunction = async function() {
// Test code here
}
}
// In browser console
await window.myTestFunction()
Common Gotchas
DB Graphs Specific
- Tags must be in page title - No
addTag()API - Properties auto-namespaced - Can't modify
:block/tagsdirectly - Reserved names drop subsequent properties - Use alternative names
- HTML must be converted to markdown - Use Turndown
- Cannot set class schemas programmatically - Use
upsertProperty()andupsertBlockProperty()instead - TypeScript defs lag behind - Use
as anyfor schema parameters - Query by unique properties - Not by page titles
Both MD and DB
- React StrictMode causes double execution - Avoid if possible
- Empty properties auto-hidden - Set to
nullor""to preserve - Case-insensitive tag lookup -
#Tagand#tagare the same
API Differences Summary
| Feature | MD Graphs | DB Graphs |
|---|---|---|
| Page creation | Simple properties | Schema-based with types |
| Property storage | Frontmatter | Namespaced database entities |
| Tags | Property or content | Must be in page title |
| HTML content | Direct insertion | Convert to markdown |
| Duplicate detection | By filename | By unique property query |
| Class pages | Not applicable | Schema inheritance support |
| Block properties | Frontmatter | Database properties |
Known Limitations (v0.2.3)
- No tag insertion API - Must use
#tagin title - Reserved date properties partially broken - Drop subsequent properties
- Cannot set class property schemas programmatically -
:logseq.property.class/propertiesis a system property that plugins cannot modify. Users must manually configure class schemas in the UI. - TypeScript definitions incomplete -
schemaandclassparameters not defined
Class Schema Workaround
Since plugins cannot set class schemas, use this pattern:
// Create property entities on plugin init
async function initProperties() {
const properties = {
year: { type: 'number' },
title: { type: 'string' },
verified: { type: 'checkbox' }
}
for (const [name, schema] of Object.entries(properties)) {
await logseq.Editor.upsertProperty(name, schema)
}
}
// Use explicit schemas when setting properties on pages
async function createPage(data) {
const page = await logseq.Editor.createPage(
`${data.title} #mytag`,
{}
)
await logseq.Editor.upsertBlockProperty(
page.uuid, 'year', data.year, { type: 'number' }
)
await logseq.Editor.upsertBlockProperty(
page.uuid, 'title', data.title, { type: 'string' }
)
}
This provides typed properties without requiring class schema setup.
Requirements
- Logseq: 0.11.0+ (for DB graphs)
- @logseq/libs: 0.2.3+
- Build tool: vite-plugin-logseq recommended
- Testing: Must test on actual DB graphs, not MD graphs
Best Practices
- Create property entities first - Use
upsertProperty()to define reusable properties with schemas - Use explicit schemas - Pass schema parameter to
upsertBlockProperty()for type safety - Alternative names for reserved properties - Avoid
created,modified,updated - Tags in page title - Include tags using
#tagsyntax, not as properties - Convert HTML to markdown - Use Turndown for DB graphs
- Query by unique properties - Use DOI, URL, etc. for duplicate detection, not page titles
- Critical properties first - Put important properties early (validation failures drop subsequent ones)
- Expose test functions - Use
parent.windowfor console testing - Type assertions - Use
as anyfor schema parameters until TypeScript defs updated - Document manual setup - If class schemas are important for your use case, provide user instructions for manual configuration
GitHub Repository
Related Skills
sglang
MetaSGLang is a high-performance LLM serving framework that specializes in fast, structured generation for JSON, regex, and agentic workflows using its RadixAttention prefix caching. It delivers significantly faster inference, especially for tasks with repeated prefixes, making it ideal for complex, structured outputs and multi-turn conversations. Choose SGLang over alternatives like vLLM when you need constrained decoding or are building applications with extensive prefix sharing.
evaluating-llms-harness
TestingThis Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.
content-collections
MetaThis skill provides a production-tested setup for Content Collections, a TypeScript-first tool that transforms Markdown/MDX files into type-safe data collections with Zod validation. Use it when building blogs, documentation sites, or content-heavy Vite + React applications to ensure type safety and automatic content validation. It covers everything from Vite plugin configuration and MDX compilation to deployment optimization and schema validation.
llamaguard
OtherLlamaGuard is Meta's 7-8B parameter model for moderating LLM inputs and outputs across six safety categories like violence and hate speech. It offers 94-95% accuracy and can be deployed using vLLM, Hugging Face, or Amazon SageMaker. Use this skill to easily integrate content filtering and safety guardrails into your AI applications.
