Logseq has two storage modes:
key:: value in markdown filesThis guide covers DB-based properties API for plugin development.
Properties must be defined globally before use. Think of it as creating columns in a database.
Tags act as classes/types. Pages/blocks can be tagged to inherit property schemas.
After defining properties, assign values to specific blocks/pages.
upsertProperty(name, options)Define or update a property schema globally.
Parameters:
name (string): Property nameoptions (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' })
createTag(name, options? )Create a tag as a class/type definition.
Parameters:
name (string): Tag nameoptions (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 UUIDpropertyName (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')
upsertBlockProperty(blockUUID, propertyName, value)Set property value for a specific block or page.
Parameters:
blockUUID (string): Block or page UUIDpropertyName (string): Property namevalue (any): Property value
node type with cardinality: 'many': Use array of page IDsnode type with cardinality: 'one': Use single page IDExample:
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 UUIDtagUUID (string): Tag UUIDExample:
const page = await logseq. Editor.createPage('Moby Dick')
const bookTag = await logseq.Editor.getTag('book')
await logseq.Editor.addBlockTag(page.uuid, bookTag.uuid)
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
// )
const prop = await logseq.Editor. upsertProperty('optionalField', { type: 'default' })
await logseq.Editor.upsertBlockProperty(
prop.uuid,
': logseq.property/hide-empty-value',
true
)
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')
}
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
}
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
)
}
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)
}
}
}
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)
}
Always check if tags/pages exist before creating:
let tag = await logseq.Editor.getTag('book')
if (!tag) {
tag = await logseq.Editor.createTag('book')
}
upsertPropertycreateTagaddTagPropertyaddBlockTagupsertBlockPropertyWhen setting node type properties, use .id not .uuid:
await logseq.Editor.upsertBlockProperty(
page. uuid,
'category',
categoryPage.id // ✅ Correct
// categoryPage.uuid // ❌ Wrong
)
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
}
See complete implementation: xyhp915/logseq-zotero-plugin
Key techniques:
z_item_types. json metadataZotero → book, article, etc.v5(itemKey, namespace)@logseq/libs package