|
| 1 | +# Property & Tag Idents |
| 2 | + |
| 3 | +## What is an Ident? |
| 4 | + |
| 5 | +An **ident** is a unique keyword identifier for a property or tag in the database. It's distinct from: |
| 6 | +- **name**: Human-readable display name (e.g., "Created At") |
| 7 | +- **uuid**: Universal unique identifier for the entity |
| 8 | +- **ident**: Internal database identifier (e.g., `:logseq.property/created-at`) |
| 9 | + |
| 10 | +**Key characteristics:** |
| 11 | +- Idents use kebab-case with namespace prefixes |
| 12 | +- System properties use `:logseq.property/` namespace |
| 13 | +- System class properties use `:logseq.class/` namespace |
| 14 | +- Plugin created class use `:plugin.class.xxx-plugin-id/` namespace |
| 15 | +- Plugin created property use `:plugin.property.xxx-plugin-id/` namespace |
| 16 | +- Custom properties typically don't have explicit idents (auto-generated) |
| 17 | + |
| 18 | +## When to Use Idents |
| 19 | + |
| 20 | +Use idents when: |
| 21 | +1. Working with **built-in system properties** |
| 22 | +2. Setting **special behaviors** for properties |
| 23 | +3. Querying the database directly with datascript |
| 24 | +4. Creating **stable references** that survive renames |
| 25 | + |
| 26 | +**Example:** |
| 27 | +```typescript |
| 28 | +// Using ident for system property |
| 29 | +await logseq.Editor. upsertBlockProperty( |
| 30 | + blockId, |
| 31 | + ':logseq. property/created-at', // ident |
| 32 | + new Date().getTime() |
| 33 | +) |
| 34 | + |
| 35 | +// Using regular property name |
| 36 | +await logseq.Editor. upsertBlockProperty( |
| 37 | + blockId, |
| 38 | + 'author', // regular name |
| 39 | + 'John Doe' |
| 40 | +) |
| 41 | +``` |
| 42 | + |
| 43 | +--- |
| 44 | + |
| 45 | +## Built-in System Properties |
| 46 | + |
| 47 | +### Common System Properties |
| 48 | + |
| 49 | +#### `:logseq.property/created-at` |
| 50 | +Timestamp when the block/page was created. |
| 51 | + |
| 52 | +```typescript |
| 53 | +await logseq.Editor.upsertBlockProperty( |
| 54 | + page.uuid, |
| 55 | + ':logseq.property/created-at', |
| 56 | + Date.now() |
| 57 | +) |
| 58 | +``` |
| 59 | + |
| 60 | +#### `:logseq.property/updated-at` |
| 61 | +Timestamp when the block/page was last updated. |
| 62 | + |
| 63 | +```typescript |
| 64 | +await logseq.Editor.upsertBlockProperty( |
| 65 | + block.uuid, |
| 66 | + ':logseq.property/updated-at', |
| 67 | + Date.now() |
| 68 | +) |
| 69 | +``` |
| 70 | + |
| 71 | +#### `:logseq.property/icon` |
| 72 | +Icon for a page or block (set via `setBlockIcon` API). |
| 73 | + |
| 74 | +```typescript |
| 75 | +// Don't set directly, use API: |
| 76 | +await logseq.Editor. setBlockIcon(page.uuid, 'emoji', 'book') |
| 77 | + |
| 78 | +// Behind the scenes, this sets: |
| 79 | +// :logseq.property/icon {: type "emoji", :id "book"} |
| 80 | +``` |
| 81 | + |
| 82 | +#### `:logseq.property/hide-empty-value` |
| 83 | +Hide property when value is empty. |
| 84 | + |
| 85 | +```typescript |
| 86 | +const prop = await logseq.Editor.upsertProperty('optional-field', { |
| 87 | + type: 'default' |
| 88 | +}) |
| 89 | + |
| 90 | +await logseq.Editor.upsertBlockProperty( |
| 91 | + prop. id, // Apply to property entity itself |
| 92 | + ':logseq.property/hide-empty-value', |
| 93 | + true |
| 94 | +) |
| 95 | +``` |
| 96 | + |
| 97 | +#### `:logseq.property/closed-value-mode` |
| 98 | +Property only accepts predefined values (enum-like). |
| 99 | + |
| 100 | +```typescript |
| 101 | +const prop = await logseq.Editor. upsertProperty('status', { type: 'default' }) |
| 102 | + |
| 103 | +await logseq.Editor.upsertBlockProperty( |
| 104 | + prop.id, |
| 105 | + ':logseq.property/closed-value-mode', |
| 106 | + true |
| 107 | +) |
| 108 | + |
| 109 | +// Define allowed values |
| 110 | +await logseq.Editor.upsertBlockProperty( |
| 111 | + prop. id, |
| 112 | + ':logseq.property/closed-values', |
| 113 | + ['todo', 'in-progress', 'done'] |
| 114 | +) |
| 115 | +``` |
| 116 | + |
| 117 | +#### `:logseq.property/schema` |
| 118 | +Property schema definition (type, cardinality, etc.). |
| 119 | + |
| 120 | +```typescript |
| 121 | +// Automatically set by upsertProperty, don't set manually |
| 122 | +await logseq.Editor. upsertProperty('tags', { |
| 123 | + type: 'node', |
| 124 | + cardinality: 'many' |
| 125 | +}) |
| 126 | + |
| 127 | +// Behind the scenes sets: |
| 128 | +// :logseq. property/schema {:type "node", :cardinality "many"} |
| 129 | +``` |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +## Built-in Class (Tag) Properties |
| 134 | + |
| 135 | +### Tag Inheritance & Schema |
| 136 | + |
| 137 | +#### `:logseq.property. class/extends` |
| 138 | +Define parent classes (tag inheritance). |
| 139 | + |
| 140 | +```typescript |
| 141 | +const mediaTag = await logseq.Editor.createTag('Media') |
| 142 | +const bookTag = await logseq. Editor.createTag('book') |
| 143 | + |
| 144 | +// Use addTagExtends API (recommended) |
| 145 | +await logseq.Editor. addTagExtends(bookTag. uuid, mediaTag.uuid) |
| 146 | + |
| 147 | +// Or set directly: |
| 148 | +await logseq.Editor. upsertBlockProperty( |
| 149 | + bookTag.uuid, |
| 150 | + ': logseq.property.class/extends', |
| 151 | + [mediaTag.id] // Array of parent tag IDs |
| 152 | +) |
| 153 | +``` |
| 154 | + |
| 155 | +#### `:logseq.property.class/properties` |
| 156 | +Properties available for this class. |
| 157 | + |
| 158 | +```typescript |
| 159 | +// Use addTagProperty API (recommended) |
| 160 | +await logseq.Editor.addTagProperty(bookTag.uuid, 'author') |
| 161 | + |
| 162 | +// Or set directly: |
| 163 | +const authorProp = await logseq.Editor.getProperty('author') |
| 164 | +await logseq.Editor.upsertBlockProperty( |
| 165 | + bookTag.uuid, |
| 166 | + ':logseq.property. class/properties', |
| 167 | + [authorProp.id] |
| 168 | +) |
| 169 | +``` |
| 170 | + |
| 171 | +--- |
| 172 | + |
| 173 | +## Working with Idents |
| 174 | + |
| 175 | +### Pattern 1: Query by Ident |
| 176 | + |
| 177 | +```typescript |
| 178 | +// Get property entity by ident |
| 179 | +const createdAtProp = await logseq. DB.datascriptQuery(` |
| 180 | + [: find (pull ?p [*]) |
| 181 | + :where |
| 182 | + [?p :block/type "property"] |
| 183 | + [?p :block/ident : logseq.property/created-at]] |
| 184 | +`) |
| 185 | + |
| 186 | +console.log(createdAtProp) |
| 187 | +``` |
| 188 | + |
| 189 | +### Pattern 2: Check if Property Has Ident |
| 190 | + |
| 191 | +```typescript |
| 192 | +async function getPropertyIdent(propertyName: string) { |
| 193 | + const prop = await logseq.Editor. getProperty(propertyName) |
| 194 | + return prop?.ident || null |
| 195 | +} |
| 196 | + |
| 197 | +const ident = await getPropertyIdent('created-at') |
| 198 | +// Returns: ": logseq.property/created-at" or null |
| 199 | +``` |
| 200 | + |
| 201 | +### Pattern 3: Create Custom Namespaced Property |
| 202 | + |
| 203 | +```typescript |
| 204 | +// For plugin-specific properties, use your own namespace |
| 205 | +const pluginNamespace = 'myplugin' |
| 206 | + |
| 207 | +async function createPluginProperty(name: string, schema: any) { |
| 208 | + // Note: Custom idents are not directly settable via API |
| 209 | + // They're auto-generated, but you can query by name |
| 210 | + |
| 211 | + const prop = await logseq.Editor.upsertProperty(name, schema) |
| 212 | + |
| 213 | + // Custom properties won't have idents unless they're system properties |
| 214 | + return prop |
| 215 | +} |
| 216 | +``` |
| 217 | + |
| 218 | +### Pattern 4: Set Multiple System Properties |
| 219 | + |
| 220 | +```typescript |
| 221 | +async function setupPageMetadata(pageUUID: string, metadata: any) { |
| 222 | + const now = Date.now() |
| 223 | + |
| 224 | + // Set creation timestamp |
| 225 | + await logseq. Editor.upsertBlockProperty( |
| 226 | + pageUUID, |
| 227 | + ':logseq.property/created-at', |
| 228 | + metadata.createdAt || now |
| 229 | + ) |
| 230 | + |
| 231 | + // Set update timestamp |
| 232 | + await logseq. Editor.upsertBlockProperty( |
| 233 | + pageUUID, |
| 234 | + ':logseq.property/updated-at', |
| 235 | + now |
| 236 | + ) |
| 237 | + |
| 238 | + // Set custom properties |
| 239 | + await logseq. Editor.upsertBlockProperty(pageUUID, 'author', metadata.author) |
| 240 | + await logseq.Editor.upsertBlockProperty(pageUUID, 'source', metadata.source) |
| 241 | +} |
| 242 | +``` |
| 243 | + |
| 244 | +### Pattern 5: Configure Property Behavior |
| 245 | + |
| 246 | +```typescript |
| 247 | +async function createEnumProperty(name: string, allowedValues: string[]) { |
| 248 | + // 1. Create property |
| 249 | + const prop = await logseq.Editor.upsertProperty(name, { type: 'default' }) |
| 250 | + |
| 251 | + // 2. Enable closed-value mode |
| 252 | + await logseq.Editor.upsertBlockProperty( |
| 253 | + prop.id, |
| 254 | + ':logseq.property/closed-value-mode', |
| 255 | + true |
| 256 | + ) |
| 257 | + |
| 258 | + // 3. Set allowed values |
| 259 | + await logseq.Editor. upsertBlockProperty( |
| 260 | + prop.id, |
| 261 | + ':logseq.property/closed-values', |
| 262 | + allowedValues |
| 263 | + ) |
| 264 | + |
| 265 | + // 4. Optionally hide when empty |
| 266 | + await logseq. Editor.upsertBlockProperty( |
| 267 | + prop.id, |
| 268 | + ':logseq.property/hide-empty-value', |
| 269 | + true |
| 270 | + ) |
| 271 | + |
| 272 | + return prop |
| 273 | +} |
| 274 | + |
| 275 | +// Usage |
| 276 | +await createEnumProperty('priority', ['high', 'medium', 'low']) |
| 277 | +``` |
| 278 | + |
| 279 | +--- |
| 280 | + |
| 281 | +## System Property Reference Table |
| 282 | + |
| 283 | +| Ident | Purpose | Value Type | Applied To | |
| 284 | +|-------|---------|------------|------------| |
| 285 | +| `:logseq.property/created-at` | Creation timestamp | number (ms) | Block/Page | |
| 286 | +| `:logseq.property/updated-at` | Update timestamp | number (ms) | Block/Page | |
| 287 | +| `:logseq.property/icon` | Icon definition | `{type, id}` object | Block/Page | |
| 288 | +| `:logseq.property/hide-empty-value` | Hide if empty | boolean | Property entity | |
| 289 | +| `:logseq.property/closed-value-mode` | Enum mode | boolean | Property entity | |
| 290 | +| `:logseq.property/closed-values` | Allowed values | array | Property entity | |
| 291 | +| `:logseq.property/schema` | Schema definition | object | Property entity | |
| 292 | +| `:logseq.property. class/extends` | Parent classes | array of IDs | Tag entity | |
| 293 | +| `:logseq.property.class/properties` | Class properties | array of IDs | Tag entity | |
| 294 | + |
| 295 | +--- |
| 296 | + |
| 297 | +## Best Practices for Idents |
| 298 | + |
| 299 | +### 1. Prefer High-Level APIs Over Direct Ident Manipulation |
| 300 | +Use specialized APIs instead of setting idents directly: |
| 301 | + |
| 302 | +```typescript |
| 303 | +// ✅ Better: Use API |
| 304 | +await logseq.Editor.addTagExtends(childTag.uuid, parentTag.uuid) |
| 305 | + |
| 306 | +// ❌ Avoid: Direct ident manipulation |
| 307 | +await logseq. Editor.upsertBlockProperty( |
| 308 | + childTag.uuid, |
| 309 | + ': logseq.property. class/extends', |
| 310 | + [parentTag.id] |
| 311 | +) |
| 312 | +``` |
| 313 | + |
| 314 | +### 2. Only Use Idents for System Properties |
| 315 | +Custom properties should use regular names: |
| 316 | + |
| 317 | +```typescript |
| 318 | +// ✅ Correct: System property with ident |
| 319 | +await logseq. Editor.upsertBlockProperty( |
| 320 | + page.uuid, |
| 321 | + ': logseq.property/created-at', |
| 322 | + Date.now() |
| 323 | +) |
| 324 | + |
| 325 | +// ✅ Correct: Custom property with name |
| 326 | +await logseq.Editor. upsertBlockProperty( |
| 327 | + page.uuid, |
| 328 | + 'author', |
| 329 | + 'John Doe' |
| 330 | +) |
| 331 | + |
| 332 | +// ❌ Wrong: Don't create custom idents |
| 333 | +await logseq.Editor. upsertBlockProperty( |
| 334 | + page.uuid, |
| 335 | + ':myplugin/custom-prop', // Not supported |
| 336 | + 'value' |
| 337 | +) |
| 338 | +``` |
| 339 | + |
| 340 | +### 3. Query System Properties by Ident in Datascript |
| 341 | +When using advanced queries, idents provide stable references: |
| 342 | + |
| 343 | +```typescript |
| 344 | +// Query all pages created in the last 7 days |
| 345 | +const recentPages = await logseq. DB.datascriptQuery(` |
| 346 | + [:find (pull ?p [*]) |
| 347 | + :where |
| 348 | + [?p :block/type "page"] |
| 349 | + [?p : logseq.property/created-at ? created] |
| 350 | + [(> ?created ${Date.now() - 7 * 24 * 60 * 60 * 1000})]] |
| 351 | +`) |
| 352 | +``` |
0 commit comments