1
0

db_properties_skill.md 17 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, schema?, opts?)

Define or update a property schema globally.

Parameters:

  • name (string): Property name
  • schema (optional object):
    • type: 'default' | 'node' | 'date' | 'number' | 'checkbox' | 'url'
    • cardinality: 'one' | 'many' (for node type)
    • hide: boolean - hide property in UI
    • public: boolean - make property public
  • opts (optional object):
    • name: string - display name override

Returns: IEntityID object with id

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' 
})

// Hidden property with custom display name
await logseq.Editor. upsertProperty('internalKey', 
  { type: 'default', hide: true },
  { name: 'Internal Key' }
)

getProperty(key)

Retrieve an existing property by key.

Returns: BlockEntity or null

Example:

const prop = await logseq.Editor. getProperty('author')
if (!prop) {
  await logseq.Editor.upsertProperty('author', { type: 'default' })
}

removeProperty(key)

Remove a property definition globally.

Example:

await logseq.Editor.removeProperty('deprecatedField')

Tag (Class) Management APIs

createTag(name, opts?)

Create a tag as a class/type definition with properties in one call.

Parameters:

  • name (string): Tag name
  • opts (optional object):
    • uuid: string - custom UUID
    • tagProperties: Array of property definitions
    • name: string - property name
    • schema: Partial - property schema
    • properties: object - additional properties
  • Returns: PageEntity or null

    Example:

    // Simple tag
    const bookTag = await logseq.Editor.createTag('book')
    
    // Tag with custom UUID
    const articleTag = await logseq.Editor.createTag('article', { 
      uuid: 'custom-uuid-123' 
    })
    
    // Tag with properties defined in one call
    const bookTag = await logseq.Editor. createTag('book', {
      uuid: generateStableUUID('book'),
      tagProperties: [
        { 
          name: 'author', 
          schema: { type: 'default' } 
        },
        { 
          name: 'isbn', 
          schema: { type: 'default' } 
        },
        { 
          name:  'tags', 
          schema: { type: 'node', cardinality: 'many' } 
        }
      ]
    })
    

    getTag(nameOrIdent)

    Retrieve an existing tag by name or entity ID.

    Returns: PageEntity or null

    Example:

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

    getTagsByName(tagName)

    Retrieve all tags matching a name (useful for namespaced tags).

    Returns: Array<PageEntity> or null

    Example:

    const tags = await logseq.Editor.getTagsByName('book')
    

    getAllTags()

    Get all tags in the current graph.

    Returns: Array<PageEntity> or null

    Example:

    const allTags = await logseq. Editor.getAllTags()
    

    addTagProperty(tagId, propertyIdOrName)

    Add a property to a tag's schema. Property must be defined via upsertProperty first.

    Parameters:

    • tagId (BlockIdentity): Tag UUID
    • propertyIdOrName (BlockIdentity): Property UUID or name

    Example:

    // Define property globally
    await logseq.Editor. upsertProperty('author', { type: 'default' })
    
    // Add to tag
    const bookTag = await logseq. Editor.createTag('book')
    await logseq.Editor. addTagProperty(bookTag.uuid, 'author')
    

    removeTagProperty(tagId, propertyIdOrName)

    Remove a property from a tag's schema.

    Example:

    await logseq.Editor.removeTagProperty(bookTag.uuid, 'deprecatedField')
    

    addTagExtends(tagId, parentTagIdOrName)

    Set tag inheritance (class extends).

    Parameters:

    • tagId (BlockIdentity): Child tag UUID
    • parentTagIdOrName (BlockIdentity): Parent tag UUID or name

    Example:

    const mediaTag = await logseq.Editor.createTag('Media')
    const bookTag = await logseq. Editor.createTag('book')
    
    // book extends Media
    await logseq.Editor. addTagExtends(bookTag. uuid, mediaTag.uuid)
    

    removeTagExtends(tagId, parentTagIdOrName)

    Remove tag inheritance.

    Example:

    await logseq.Editor.removeTagExtends(bookTag.uuid, mediaTag.uuid)
    

    Block/Page Tagging APIs

    addBlockTag(blockId, tagId)

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

    Parameters:

    • blockId (BlockIdentity): Block or page UUID
    • tagId (BlockIdentity): 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)
    

    removeBlockTag(blockId, tagId)

    Remove a tag from a block or page.

    Example:

    await logseq.Editor.removeBlockTag(page.uuid, bookTag.uuid)
    

    getTagObjects(nameOrIdent)

    Get all blocks/pages tagged with a specific tag.

    Returns: Array<BlockEntity> or null

    Example:

    const allBooks = await logseq.Editor.getTagObjects('book')
    

    Block/Page Property Value APIs

    upsertBlockProperty(blockUUID, key, value, options?)

    Set property value for a specific block or page.

    Parameters:

    • blockUUID (BlockIdentity | EntityID): Block or page UUID or entity ID
    • key (string): Property name
    • value (any): Property value
      • For node type with cardinality: 'many': Use array of page IDs (.id not .uuid)
      • For node type with cardinality: 'one': Use single page ID
      • For other types: Use primitive values
    • options (optional object):
      • reset: boolean - replace instead of merge

    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 (use . id not .uuid)
    await logseq.Editor.upsertBlockProperty(page.uuid, 'category', categoryPage.id)
    
    // Set multiple references (use . id not .uuid)
    await logseq.Editor.upsertBlockProperty(
      page.uuid, 
      'tags', 
      [tagPage1.id, tagPage2.id, tagPage3.id]
    )
    
    // Reset property value completely
    await logseq.Editor. upsertBlockProperty(
      page.uuid, 
      'tags', 
      [newTag1.id], 
      { reset: true }
    )
    

    getBlockProperty(blockId, key)

    Get a single property value from a block or page.

    Returns: BlockEntity or null

    Example:

    const author = await logseq.Editor. getBlockProperty(page.uuid, 'author')
    

    getBlockProperties(blockId)

    Get all properties from a block.

    Returns: Record<string, any> or null

    Example:

    const props = await logseq.Editor. getBlockProperties(page.uuid)
    console.log(props. author, props.isbn)
    

    getPageProperties(pageId)

    Get all properties from a page.

    Returns: Record<string, any> or null

    Example:

    const props = await logseq.Editor.getPageProperties(page.uuid)
    

    removeBlockProperty(blockId, key)

    Remove a property from a block or page.

    Example:

    await logseq.Editor.removeBlockProperty(page.uuid, 'deprecatedField')
    

    Special Properties & Utilities

    Hide Empty Values

    Use special property key :logseq. property/hide-empty-value:

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

    Set Block Icon

    // Tabler icon
    await logseq.Editor.setBlockIcon(blockId, 'tabler-icon', 'book')
    
    // Emoji (from emoji-mart)
    await logseq.Editor.setBlockIcon(blockId, 'emoji', 'books')
    

    Remove Block Icon

    await logseq.Editor.removeBlockIcon(blockId)
    

    Common Patterns

    Pattern 1: Define Schema with Tag and Properties (Simple)

    async function setupBookSchema() {
      // Create tag with all properties in one call
      const bookTag = await logseq.Editor.createTag('book', {
        uuid: generateStableUUID('book'),
        tagProperties: [
          { name: 'title', schema: { type: 'default' } },
          { name: 'author', schema: { type: 'default' } },
          { name: 'isbn', schema: { type: 'default' } },
          { name: 'year', schema: { type: 'number' } },
          { name:  'tags', schema: { type: 'node', cardinality: 'many' } }
        ]
      })
      
      return bookTag
    }
    

    Pattern 2: Define Schema with Inheritance

    async function setupSchemaWithInheritance() {
      // 1. Create parent tag
      const mediaTag = await logseq.Editor.createTag('Media', {
        tagProperties: [
          { name:  'title', schema: { type: 'default' } },
          { name: 'year', schema: { type: 'number' } },
          { name: 'tags', schema: { type: 'node', cardinality: 'many' } }
        ]
      })
      
      // 2. Create child tag with specific properties
      const bookTag = await logseq.Editor.createTag('book', {
        tagProperties: [
          { name:  'author', schema: { type: 'default' } },
          { name: 'isbn', schema: { type: 'default' } }
        ]
      })
      
      // 3. Set inheritance
      await logseq. Editor.addTagExtends(bookTag.uuid, mediaTag.uuid)
      
      return { mediaTag, bookTag }
    }
    

    Pattern 3: Create Instance with Properties

    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 4: 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 not .uuid for references
        })
      )
      
      // Link book to categories
      await logseq.Editor.upsertBlockProperty(
        bookPageUUID, 
        'tags',  // Must be 'node' type with 'many' cardinality
        categoryIDs
      )
    }
    

    Pattern 5: Batch Schema Setup from Configuration

    async function setupSchemaFromConfig(config) {
      // config example:
      // {
      //   "Media": { 
      //     "properties": ["title", "year"],
      //     "parent": null 
      //   },
      //   "book": { 
      //     "properties": ["author", "isbn"],
      //     "parent": "Media" 
      //   }
      // }
      
      for (const [tagName, tagConfig] of Object. entries(config)) {
        let tag = await logseq.Editor. getTag(tagName)
        
        if (!tag) {
          // Create tag with properties
          tag = await logseq.Editor.createTag(tagName, {
            tagProperties:  tagConfig.properties. map(name => ({
              name,
              schema: { type: 'default' }
            }))
          })
          
          // Set parent if specified
          if (tagConfig.parent) {
            const parentTag = await logseq. Editor.getTag(tagConfig. parent)
            if (parentTag) {
              await logseq.Editor.addTagExtends(tag.uuid, parentTag.uuid)
            }
          }
        }
      }
    }
    

    Pattern 6: Migrate Existing Pages to New Schema

    async function migrateToBookSchema() {
      // 1. Setup schema
      const bookTag = await logseq.Editor.createTag('book', {
        tagProperties: [
          { name: 'author', schema: { type: 'default' } },
          { name: 'year', schema: { type: 'number' } }
        ]
      })
      
      // 2. Find all pages with old structure
      const allPages = await logseq.Editor.getAllPages()
      
      // 3. Migrate each page
      for (const page of allPages) {
        const props = await logseq.Editor. getPageProperties(page.uuid)
        
        // Check if page has book-like properties
        if (props?. author || props?.isbn) {
          // Add book tag
          await logseq.Editor.addBlockTag(page.uuid, bookTag.uuid)
          
          // Migrate properties if needed
          if (props. author) {
            await logseq.Editor.upsertBlockProperty(
              page.uuid, 
              'author', 
              props. author
            )
          }
        }
      }
    }
    

    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/properties exist before creating:

    let tag = await logseq.Editor.getTag('book')
    if (!tag) {
      tag = await logseq.Editor.createTag('book', {
        tagProperties: [/* ... */]
      })
    }
    

    3. Property Definition Order (Manual Approach)

    If not using createTag with tagProperties:

    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:

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

    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}`)
      await logseq.UI.showMsg(`Error:  ${error. message}`, 'error')
    }
    

    6. Use createTag with tagProperties

    Prefer the streamlined approach for better readability:

    // ✅ Better:  One call
    const tag = await logseq.Editor. createTag('book', {
      tagProperties: [
        { name: 'author', schema: { type: 'default' } },
        { name: 'isbn', schema: { type: 'default' } }
      ]
    })
    
    // ❌ Verbose:  Multiple calls
    await logseq. Editor.upsertProperty('author', { type: 'default' })
    await logseq.Editor.upsertProperty('isbn', { type: 'default' })
    const tag = await logseq.Editor. createTag('book')
    await logseq.Editor. addTagProperty(tag.uuid, 'author')
    await logseq.Editor.addTagProperty(tag.uuid, 'isbn')
    

    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

    Type Definitions

    type PropertySchema = {
      type: 'default' | 'number' | 'node' | 'date' | 'checkbox' | 'url' | string
      cardinality:  'many' | 'one'
      hide:  boolean
      public: boolean
    }
    
    type BlockIdentity = BlockUUID | Pick<BlockEntity, 'uuid'>
    type PageIdentity = BlockPageName | BlockIdentity
    type EntityID = number
    

    Reference