db_properties_skill.md 10 KB

Logseq DB Properties SDK Reference

Overview

Logseq has two storage modes:

  • File-based: Properties stored as key:: value in markdown files
  • DB-based: Properties stored in SQLite with structured schema, types, and relationships

This guide covers DB-based properties API for plugin development.


Core Concepts

1. Property Schema (Global Definition)

Properties must be defined globally before use. Think of it as creating columns in a database.

2. Tags as Classes

Tags act as classes/types. Pages/blocks can be tagged to inherit property schemas.

3. Property Values

After defining properties, assign values to specific blocks/pages.


API Reference

Property Schema APIs

upsertProperty(name, options)

Define or update a property schema globally.

Parameters:

  • name (string): Property name
  • options (object):
    • type: 'default' | 'node' | 'date' | 'number'
    • cardinality: 'one' | 'many' (for node type)

Returns: Property object with uuid

Example:

// Simple text property
await logseq.Editor.upsertProperty('author', { type: 'default' })

// Reference property (links to other pages)
await logseq.Editor.upsertProperty('tags', { 
  type: 'node', 
  cardinality: 'many' 
})

// Date property
await logseq.Editor.upsertProperty('publishedDate', { type: 'date' })

Tag (Class) Management APIs

createTag(name, options? )

Create a tag as a class/type definition.

Parameters:

  • name (string): Tag name
  • options (optional object): { uuid: string }

Returns: Tag object with uuid and id

Example:

const bookTag = await logseq.Editor.createTag('book')
const articleTag = await logseq.Editor.createTag('article', { 
  uuid: 'custom-uuid-123' 
})

getTag(name)

Retrieve an existing tag by name.

Returns: Tag object or null

Example:

const tag = await logseq.Editor. getTag('book')
if (! tag) {
  // Tag doesn't exist, create it
}

addTagProperty(tagUUID, propertyName)

Add a property to a tag's schema. Pages tagged with this tag will have this property available.

Parameters:

  • tagUUID (string): Tag UUID
  • propertyName (string): Property name (must be defined via upsertProperty first)

Example:

// Define properties globally
await logseq.Editor. upsertProperty('author', { type: 'default' })
await logseq.Editor.upsertProperty('isbn', { type: 'default' })

// Add properties to 'book' tag
const bookTag = await logseq. Editor.createTag('book')
await logseq.Editor.addTagProperty(bookTag.uuid, 'author')
await logseq.Editor.addTagProperty(bookTag.uuid, 'isbn')

Block/Page Property Value APIs

upsertBlockProperty(blockUUID, propertyName, value)

Set property value for a specific block or page.

Parameters:

  • blockUUID (string): Block or page UUID
  • propertyName (string): Property name
  • value (any): Property value
    • For node type with cardinality: 'many': Use array of page IDs
    • For node type with cardinality: 'one': Use single page ID
    • For other types: Use primitive values

Example:

const page = await logseq.Editor.getPage('my-page-uuid')

// Set simple value
await logseq.Editor.upsertBlockProperty(page.uuid, 'author', 'John Doe')

// Set number
await logseq.Editor.upsertBlockProperty(page.uuid, 'year', 2024)

// Set single reference
await logseq.Editor. upsertBlockProperty(page.uuid, 'category', categoryPageID)

// Set multiple references
await logseq.Editor.upsertBlockProperty(
  page.uuid, 
  'tags', 
  [tagPageID1, tagPageID2, tagPageID3]
)

addBlockTag(blockUUID, tagUUID)

Tag a block or page, making it an instance of that tag's class.

Parameters:

  • blockUUID (string): Block or page UUID
  • tagUUID (string): Tag UUID

Example:

const page = await logseq. Editor.createPage('Moby Dick')
const bookTag = await logseq.Editor.getTag('book')
await logseq.Editor.addBlockTag(page.uuid, bookTag.uuid)

Tag Inheritance

Set Tag Parent (Class Inheritance)

Invoke API of logseq.Editor.setTagExtends

Example:

// Create parent tag
const rootTag = await logseq.Editor.createTag('Media')

// Create child tag
const bookTag = await logseq.Editor.createTag('book')

// Set inheritance
await logseq.Editor.addTagExtends(bookTag.uuid, rootTag.uuid)

// Use special property `:logseq.property.class/extends` to create tag hierarchy.
// await logseq.Editor. upsertBlockProperty(
//   bookTag.uuid, 
//   ':logseq.property.class/extends', 
//   [rootTag.id]  // Use . id, not .uuid
// )

Special Properties

Hide Empty Values

const prop = await logseq.Editor. upsertProperty('optionalField', { type: 'default' })
await logseq.Editor.upsertBlockProperty(
  prop.uuid, 
  ': logseq.property/hide-empty-value', 
  true
)

Common Patterns

Pattern 1: Define a Schema with Class Hierarchy

async function setupBookSchema() {
  // 1. Create root class
  const mediaTag = await logseq.Editor.createTag('Media')
  
  // 2. Define common properties
  await logseq. Editor.upsertProperty('title', { type: 'default' })
  await logseq.Editor.upsertProperty('year', { type: 'number' })
  await logseq.Editor.upsertProperty('tags', { type: 'node', cardinality: 'many' })
  
  // 3. Add properties to root class
  await logseq. Editor.addTagProperty(mediaTag.uuid, 'title')
  await logseq.Editor.addTagProperty(mediaTag.uuid, 'year')
  await logseq.Editor. addTagProperty(mediaTag.uuid, 'tags')
  
  // 4. Create subclass
  const bookTag = await logseq.Editor.createTag('book')
  await logseq.Editor.upsertBlockProperty(
    bookTag.uuid, 
    ':logseq.property.class/extends', 
    [mediaTag.id]
  )
  
  // 5. Add book-specific properties
  await logseq.Editor.upsertProperty('author', { type: 'default' })
  await logseq.Editor.upsertProperty('isbn', { type: 'default' })
  await logseq.Editor.addTagProperty(bookTag.uuid, 'author')
  await logseq.Editor.addTagProperty(bookTag.uuid, 'isbn')
}

Pattern 2: Create an Instance

async function createBookInstance(bookData) {
  // 1. Create page with stable UUID
  const pageUUID = generateStableUUID(bookData.id)
  let page = await logseq.Editor.getPage(pageUUID)
  
  if (!page) {
    page = await logseq.Editor.createPage(
      bookData.title, 
      {}, 
      { customUUID: pageUUID, redirect: false }
    )
  }
  
  // 2. Tag the page
  const bookTag = await logseq.Editor.getTag('book')
  await logseq.Editor.addBlockTag(page.uuid, bookTag. uuid)
  
  // 3. Set property values
  await logseq. Editor.upsertBlockProperty(page.uuid, 'title', bookData.title)
  await logseq.Editor.upsertBlockProperty(page.uuid, 'author', bookData.author)
  await logseq.Editor.upsertBlockProperty(page.uuid, 'isbn', bookData.isbn)
  await logseq.Editor.upsertBlockProperty(page.uuid, 'year', bookData.year)
  
  return page. uuid
}

Pattern 3: Link Related Entities

async function linkBookToCategories(bookPageUUID, categoryKeys) {
  // Get or create category pages
  const categoryIDs = await Promise.all(
    categoryKeys.map(async (key) => {
      const catUUID = generateStableUUID(key)
      let catPage = await logseq.Editor.getPage(catUUID)
      
      if (!catPage) {
        catPage = await logseq.Editor.createPage(key, {}, { 
          customUUID: catUUID 
        })
      }
      
      return catPage.id  // Use . id for references
    })
  )
  
  // Link book to categories
  await logseq.Editor.upsertBlockProperty(
    bookPageUUID, 
    'tags',  // Must be 'node' type with 'many' cardinality
    categoryIDs
  )
}

Pattern 4: Batch Schema Setup from JSON

async function setupSchemaFromConfig(config) {
  // config = { "book": { "fields": ["author", "isbn"], "parent": "Media" } }
  
  for (const [tagName, tagConfig] of Object.entries(config)) {
    let tag = await logseq.Editor.getTag(tagName)
    
    if (!tag) {
      tag = await logseq.Editor.createTag(tagName)
      
      // Set parent if specified
      if (tagConfig. parent) {
        const parentTag = await logseq. Editor.getTag(tagConfig. parent)
        if (parentTag) {
          await logseq.Editor.upsertBlockProperty(
            tag. uuid, 
            ':logseq.property.class/extends', 
            [parentTag.id]
          )
        }
      }
    }
    
    // Add properties
    for (const fieldName of tagConfig.fields) {
      await logseq.Editor.upsertProperty(fieldName, { type: 'default' })
      await logseq.Editor.addTagProperty(tag.uuid, fieldName)
    }
  }
}

Best Practices

1. Use Stable UUIDs

Generate deterministic UUIDs from stable identifiers:

import { v5 as uuidv5 } from 'uuid'

const NAMESPACE = 'your-plugin-namespace-uuid'

function generateStableUUID(id:  string): string {
  return uuidv5(id, NAMESPACE)
}

2. Check Before Creating

Always check if tags/pages exist before creating:

let tag = await logseq.Editor.getTag('book')
if (!tag) {
  tag = await logseq.Editor.createTag('book')
}

3. Property Definition Order

  1. Define property globally with upsertProperty
  2. Create tag with createTag
  3. Add property to tag with addTagProperty
  4. Create page and tag it with addBlockTag
  5. Set values with upsertBlockProperty

4. Use . id for References

When setting node type properties, use .id not .uuid:

await logseq.Editor.upsertBlockProperty(
  page. uuid, 
  'category', 
  categoryPage.id  // ✅ Correct
  // categoryPage.uuid  // ❌ Wrong
)

5. Error Handling

Wrap API calls in try-catch for robustness:

try {
  await logseq.Editor.upsertBlockProperty(uuid, 'field', value)
} catch (error) {
  console.error(`Failed to set property: ${error}`)
  // Handle gracefully
}

Real-World Example: Zotero Plugin

See complete implementation: xyhp915/logseq-zotero-plugin

Key techniques:

  • Schema defined from z_item_types. json metadata
  • Tag hierarchy: Zoterobook, article, etc.
  • Stable UUIDs via v5(itemKey, namespace)
  • Relationship properties for collections
  • Nested blocks for attachments

Reference

  • Type Definitions: See @logseq/libs package
  • Examples: logseq-zotero-plugin/handlers.ts
  • Search More: GitHub Code Search