chore: update db_properties_skill.md with enhanced property schema APIs and examples

This commit is contained in:
charlie
2026-01-04 16:36:02 +08:00
parent a2bfb39632
commit 2642765ebf

View File

@@ -3,8 +3,8 @@
## 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
- **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.
@@ -27,16 +27,20 @@ After defining properties, assign values to specific blocks/pages.
### Property Schema APIs
#### `upsertProperty(name, options)`
#### `upsertProperty(name, schema?, opts?)`
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)
- `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:** Property object with `uuid`
**Returns:** `IEntityID` object with `id`
**Example:**
```typescript
@@ -49,77 +53,216 @@ await logseq.Editor.upsertProperty('tags', {
cardinality: 'many'
})
// Date property
await logseq.Editor.upsertProperty('publishedDate', { type: 'date' })
// 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:**
```typescript
const prop = await logseq.Editor. getProperty('author')
if (!prop) {
await logseq.Editor.upsertProperty('author', { type: 'default' })
}
```
#### `removeProperty(key)`
Remove a property definition globally.
**Example:**
```typescript
await logseq.Editor.removeProperty('deprecatedField')
```
---
### Tag (Class) Management APIs
#### `createTag(name, options? )`
Create a tag as a class/type definition.
#### `createTag(name, opts?)`
Create a tag as a class/type definition with properties in one call.
**Parameters:**
- `name` (string): Tag name
- `options` (optional object): `{ uuid: string }`
- `opts` (optional object):
- `uuid`: string - custom UUID
- `tagProperties`: Array of property definitions
- `name`: string - property name
- `schema`: Partial<PropertySchema> - property schema
- `properties`: object - additional properties
**Returns:** Tag object with `uuid` and `id`
**Returns:** `PageEntity` or `null`
**Example:**
```typescript
// 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(name)`
Retrieve an existing tag by name.
#### `getTag(nameOrIdent)`
Retrieve an existing tag by name or entity ID.
**Returns:** Tag object or `null`
**Returns:** `PageEntity` or `null`
**Example:**
```typescript
const tag = await logseq.Editor. getTag('book')
if (! tag) {
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.
#### `getTagsByName(tagName)`
Retrieve all tags matching a name (useful for namespaced tags).
**Parameters:**
- `tagUUID` (string): Tag UUID
- `propertyName` (string): Property name (must be defined via `upsertProperty` first)
**Returns:** `Array<PageEntity>` or `null`
**Example:**
```typescript
// Define properties globally
await logseq.Editor. upsertProperty('author', { type: 'default' })
await logseq.Editor.upsertProperty('isbn', { type: 'default' })
const tags = await logseq.Editor.getTagsByName('book')
```
// Add properties to 'book' tag
#### `getAllTags()`
Get all tags in the current graph.
**Returns:** `Array<PageEntity>` or `null`
**Example:**
```typescript
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:**
```typescript
// 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')
await logseq.Editor.addTagProperty(bookTag.uuid, 'isbn')
await logseq.Editor. addTagProperty(bookTag.uuid, 'author')
```
#### `removeTagProperty(tagId, propertyIdOrName)`
Remove a property from a tag's schema.
**Example:**
```typescript
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:**
```typescript
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:**
```typescript
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:**
```typescript
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:**
```typescript
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:**
```typescript
const allBooks = await logseq.Editor.getTagObjects('book')
```
---
### Block/Page Property Value APIs
#### `upsertBlockProperty(blockUUID, propertyName, value)`
#### `upsertBlockProperty(blockUUID, key, value, options?)`
Set property value for a specific block or page.
**Parameters:**
- `blockUUID` (string): Block or page UUID
- `propertyName` (string): Property name
- `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
- For `node` type with `cardinality: 'one'`: Use single page ID
- For other types: Use primitive values
- 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:**
```typescript
@@ -131,109 +274,147 @@ 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 single reference (use . id not .uuid)
await logseq.Editor.upsertBlockProperty(page.uuid, 'category', categoryPage.id)
// Set multiple references
// Set multiple references (use . id not .uuid)
await logseq.Editor.upsertBlockProperty(
page.uuid,
'tags',
[tagPageID1, tagPageID2, tagPageID3]
[tagPage1.id, tagPage2.id, tagPage3.id]
)
// Reset property value completely
await logseq.Editor. upsertBlockProperty(
page.uuid,
'tags',
[newTag1.id],
{ reset: true }
)
```
#### `addBlockTag(blockUUID, tagUUID)`
Tag a block or page, making it an instance of that tag's class.
#### `getBlockProperty(blockId, key)`
Get a single property value from a block or page.
**Parameters:**
- `blockUUID` (string): Block or page UUID
- `tagUUID` (string): Tag UUID
**Returns:** `BlockEntity` or `null`
**Example:**
```typescript
const page = await logseq. Editor.createPage('Moby Dick')
const bookTag = await logseq.Editor.getTag('book')
await logseq.Editor.addBlockTag(page.uuid, bookTag.uuid)
const author = await logseq.Editor. getBlockProperty(page.uuid, 'author')
```
#### `getBlockProperties(blockId)`
Get all properties from a block.
**Returns:** `Record<string, any>` or `null`
**Example:**
```typescript
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:**
```typescript
const props = await logseq.Editor.getPageProperties(page.uuid)
```
#### `removeBlockProperty(blockId, key)`
Remove a property from a block or page.
**Example:**
```typescript
await logseq.Editor.removeBlockProperty(page.uuid, 'deprecatedField')
```
---
### Tag Inheritance
#### Set Tag Parent (Class Inheritance)
Invoke API of `logseq.Editor.setTagExtends`
**Example:**
```typescript
// 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
### Special Properties & Utilities
#### Hide Empty Values
Use special property key `:logseq. property/hide-empty-value`:
```typescript
const prop = await logseq.Editor. upsertProperty('optionalField', { type: 'default' })
const prop = await logseq.Editor.upsertProperty('optionalField', { type: 'default' })
await logseq.Editor.upsertBlockProperty(
prop.uuid,
prop.id, // Use property's entity ID
': logseq.property/hide-empty-value',
true
)
```
#### Set Block Icon
```typescript
// Tabler icon
await logseq.Editor.setBlockIcon(blockId, 'tabler-icon', 'book')
// Emoji (from emoji-mart)
await logseq.Editor.setBlockIcon(blockId, 'emoji', 'books')
```
#### Remove Block Icon
```typescript
await logseq.Editor.removeBlockIcon(blockId)
```
---
## Common Patterns
### Pattern 1: Define a Schema with Class Hierarchy
### Pattern 1: Define Schema with Tag and Properties (Simple)
```typescript
async function setupBookSchema() {
// 1. Create root class
const mediaTag = await logseq.Editor.createTag('Media')
// 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' } }
]
})
// 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')
return bookTag
}
```
### Pattern 2: Create an Instance
### Pattern 2: Define Schema with Inheritance
```typescript
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
```typescript
async function createBookInstance(bookData) {
@@ -242,7 +423,7 @@ async function createBookInstance(bookData) {
let page = await logseq.Editor.getPage(pageUUID)
if (!page) {
page = await logseq.Editor.createPage(
page = await logseq.Editor. createPage(
bookData.title,
{},
{ customUUID: pageUUID, redirect: false }
@@ -250,26 +431,26 @@ async function createBookInstance(bookData) {
}
// 2. Tag the page
const bookTag = await logseq.Editor.getTag('book')
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, 'author', bookData. author)
await logseq.Editor.upsertBlockProperty(page.uuid, 'isbn', bookData.isbn)
await logseq.Editor.upsertBlockProperty(page.uuid, 'year', bookData.year)
await logseq.Editor.upsertBlockProperty(page.uuid, 'year', bookData. year)
return page. uuid
}
```
### Pattern 3: Link Related Entities
### Pattern 4: Link Related Entities
```typescript
async function linkBookToCategories(bookPageUUID, categoryKeys) {
// Get or create category pages
const categoryIDs = await Promise.all(
categoryKeys.map(async (key) => {
categoryKeys. map(async (key) => {
const catUUID = generateStableUUID(key)
let catPage = await logseq.Editor.getPage(catUUID)
@@ -279,7 +460,7 @@ async function linkBookToCategories(bookPageUUID, categoryKeys) {
})
}
return catPage.id // Use . id for references
return catPage.id // ⚠️ Use .id not .uuid for references
})
)
@@ -292,35 +473,78 @@ async function linkBookToCategories(bookPageUUID, categoryKeys) {
}
```
### Pattern 4: Batch Schema Setup from JSON
### Pattern 5: Batch Schema Setup from Configuration
```typescript
async function setupSchemaFromConfig(config) {
// config = { "book": { "fields": ["author", "isbn"], "parent": "Media" } }
// 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)
for (const [tagName, tagConfig] of Object. entries(config)) {
let tag = await logseq.Editor. getTag(tagName)
if (!tag) {
tag = await logseq.Editor.createTag(tagName)
// 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) {
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]
)
await logseq.Editor.addTagExtends(tag.uuid, parentTag.uuid)
}
}
}
}
}
```
### Pattern 6: Migrate Existing Pages to New Schema
```typescript
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)
// Add properties
for (const fieldName of tagConfig.fields) {
await logseq.Editor.upsertProperty(fieldName, { type: 'default' })
await logseq.Editor.addTagProperty(tag.uuid, fieldName)
// 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
)
}
}
}
}
@@ -344,31 +568,44 @@ function generateStableUUID(id: string): string {
```
### 2. Check Before Creating
Always check if tags/pages exist before creating:
Always check if tags/pages/properties exist before creating:
```typescript
let tag = await logseq.Editor.getTag('book')
if (!tag) {
tag = await logseq.Editor.createTag('book')
tag = await logseq.Editor.createTag('book', {
tagProperties: [/* ... */]
})
}
```
### 3. Property Definition Order
### 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
### 4. Use . id for References ⚠️
When setting `node` type properties, use `.id` not `.uuid`:
```typescript
const categoryPage = await logseq.Editor.getPage('category-uuid')
// ✅ Correct
await logseq.Editor.upsertBlockProperty(
page. uuid,
page.uuid,
'category',
categoryPage.id // ✅ Correct
// categoryPage.uuid // ❌ Wrong
categoryPage.id
)
// ❌ Wrong
await logseq.Editor.upsertBlockProperty(
page.uuid,
'category',
categoryPage.uuid
)
```
@@ -380,15 +617,35 @@ try {
await logseq.Editor.upsertBlockProperty(uuid, 'field', value)
} catch (error) {
console.error(`Failed to set property: ${error}`)
// Handle gracefully
await logseq.UI.showMsg(`Error: ${error. message}`, 'error')
}
```
### 6. Use createTag with tagProperties
Prefer the streamlined approach for better readability:
```typescript
// ✅ 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
## Real-World Example: Zotero Plugin
See complete implementation: [xyhp915/logseq-zotero-plugin](https://github.com/xyhp915/logseq-zotero-plugin/blob/main/src/handlers.ts#L19-L207)
See complete implementation: [xyhp915/logseq-zotero-plugin](https://github.com/xyhp915/logseq-zotero-plugin/blob/main/src/handlers. ts#L19-L207)
Key techniques:
- Schema defined from `z_item_types. json` metadata
@@ -399,8 +656,25 @@ Key techniques:
---
## Type Definitions
```typescript
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
- **Type Definitions**: See `@logseq/libs` package
- **Examples**: [logseq-zotero-plugin/handlers.ts](https://github.com/xyhp915/logseq-zotero-plugin/blob/main/src/handlers. ts)
- **Type Definitions**: `@logseq/libs` - [LSPlugin. ts](https://github.com/logseq/logseq/blob/master/libs/src/LSPlugin.ts)
- **Real Example**: [logseq-zotero-plugin/handlers.ts](https://github.com/xyhp915/logseq-zotero-plugin/blob/main/src/handlers. ts)
- **Search More**: [GitHub Code Search](https://github.com/search?type=code&q=repo:xyhp915/logseq-zotero-plugin+upsertBlockProperty)