| name | thymer-plugin |
| description | Build Thymer plugins - use when the user asks to create, modify, or debug Thymer plugins for the note-taking/project management app |
Thymer Plugin Development
References
- types.d.ts - Full SDK type definitions
- sdk-reference.md - Additional SDK notes
Use this skill when building plugins for the Thymer app (thymer.com). Thymer plugins extend the application with custom functionality like status bar items, custom views, formulas, and more.
Plugin Types
AppPlugin (Global)
Extends the entire Thymer application:
- Status bar items
- Sidebar items
- Command palette commands
- Custom panels
- Toaster notifications
CollectionPlugin
Extends a specific note Collection:
- Custom views (table, board, gallery, calendar, custom)
- Custom properties with formulas
- Navigation buttons
- Render hooks for existing views
- Custom sorting
File Structure
plugin.js # Plugin code (extends AppPlugin or CollectionPlugin)
plugin.json # Configuration (name, icon, fields, views)
Quick Reference
AppPlugin Example
export class Plugin extends AppPlugin {
onLoad() {
// Add status bar item
this.statusItem = this.ui.addStatusBarItem({
label: "My Plugin",
icon: "star",
tooltip: "Click me",
onClick: () => this.handleClick()
});
// Add command palette command
this.ui.addCommandPaletteCommand({
label: "My Command",
icon: "wand",
onSelected: () => this.doSomething()
});
// Show notification
this.ui.addToaster({
title: "Hello",
message: "Plugin loaded!",
dismissible: true,
autoDestroyTime: 3000
});
}
onUnload() {
// Cleanup (intervals, listeners, etc.)
}
}
CollectionPlugin Example
export class Plugin extends CollectionPlugin {
onLoad() {
// Define a formula property
this.properties.formula("Total", ({record}) => {
const qty = record.number("Quantity");
const price = record.number("Price");
if (qty == null || price == null) return null;
return qty * price;
});
// Custom property rendering
this.properties.render("Status", ({record, prop}) => {
const el = document.createElement('span');
el.textContent = prop.text() || 'N/A';
el.style.fontWeight = 'bold';
return el;
});
// Register custom view
this.views.register("My View", (viewContext) => {
return {
onLoad: () => {
viewContext.getElement().style.padding = "20px";
},
onRefresh: ({records}) => {
const el = viewContext.getElement();
el.innerHTML = records.map(r =>
`<div>${r.getName()}</div>`
).join('');
},
onDestroy: () => {},
onFocus: () => {},
onBlur: () => {},
onKeyboardNavigation: ({e}) => {},
onPanelResize: () => {}
};
});
// Hook into board card rendering
this.views.afterRenderBoardCard(null, ({record, element}) => {
element.style.border = '2px solid blue';
});
// Add navigation button
this.addCollectionNavigationButton({
label: "Export",
icon: "download",
onClick: ({panel}) => this.exportData(panel)
});
}
}
Key APIs
UIAPI (this.ui)
addStatusBarItem({label, icon, tooltip, onClick})- Status baraddSidebarItem({label, icon, tooltip, onClick})- SidebaraddCommandPaletteCommand({label, icon, onSelected})- CommandsaddToaster({title, message, dismissible, autoDestroyTime})- NotificationscreateButton({icon, label, onClick})- Create button elementcreateDropdown({attachedTo, options})- Dropdown menuinjectCSS(cssString)- Add global stylesgetActivePanel()- Get focused panelgetPanels()- Get all panelscreatePanel()- Create new panelregisterCustomPanelType(id, callback)- Custom panel types
DataAPI (this.data)
getAllRecords()- Get all workspace recordsgetRecord(guid)- Get record by GUIDcreateNewRecord(title)- Create recordgetAllCollections()- Get all collectionsgetActiveUsers()- Get workspace users
PropertiesAPI (this.properties) - CollectionPlugin only
formula(name, fn)- Computed propertyrender(name, fn)- Custom property renderingcustomSort(name, fn)- Custom sorting
ViewsAPI (this.views) - CollectionPlugin only
register(viewName, createHooksFn)- Custom view typeafterRenderBoardCard(viewName, fn)- Board card hookafterRenderBoardColumn(viewName, fn)- Board column hookafterRenderGalleryCard(viewName, fn)- Gallery card hookafterRenderTableCell(viewName, fn)- Table cell hookafterRenderCalendarEvent(viewName, fn)- Calendar event hook
PluginRecord
getName()- Record titleguid- Record GUIDprop(name)- Get property by namenumber(name)- Get numeric propertytext(name)- Get text propertydate(name)- Get date propertygetProperties(viewName)- Get visible propertiesgetLineItems()- Get document content
PluginProperty
number()- Get as numbertext()- Get as textdate()- Get as Datechoice()- Get choice IDset(value)- Set valuesetChoice(choiceName)- Set by choice name
plugin.json Configuration
{
"name": "My Collection",
"icon": "tools",
"description": "Collection description",
"item_name": "Item",
"ver": 1,
"show_sidebar_items": true,
"show_cmdpal_items": true,
"fields": [
{
"id": "status",
"label": "Status",
"type": "choice",
"icon": "circle",
"active": true,
"many": false,
"read_only": false,
"choices": [
{"id": "todo", "label": "To Do", "color": "0", "active": true},
{"id": "done", "label": "Done", "color": "2", "active": true}
]
},
{
"id": "amount",
"label": "Amount",
"type": "number",
"icon": "currency-dollar",
"number_format": "USD",
"active": true
}
],
"views": [
{
"id": "main",
"label": "All Items",
"type": "table",
"icon": "table",
"shown": true,
"field_ids": ["status", "amount"],
"sort_field_id": "status",
"sort_dir": "asc"
}
],
"page_field_ids": ["status", "amount"]
}
Property Types
text- Plain textnumber- Numeric (formats: plain, formatted, USD, EUR, etc.)choice- Enum/status with choicesdatetime- Date/timeuser- User referencerecord- Record referenceurl- URL linkfile- File attachmentimage- Imagedynamic- Formula-computed
View Types
table- Table viewboard- Kanban boardgallery- Card gallerycalendar- Calendar viewcustom- Fully custom viewrecord- Single record view
Icons
Common icons: star, clock, tools, settings, search, home, user, calendar, bell, check, bug, rocket, wand, heart, file-text, folder, download, chart-bar, database
See types.d.ts for full icon list.
Development Workflow
- Edit
plugin.jsandplugin.json - Run
npm run devfor hot reload - Build with
npm run build - Copy dist/plugin.js to Thymer's Edit Code dialog
Important Notes
- Always clean up in
onUnload()(intervals, listeners) - Use CSS variables for theming (--bg-default, --text-muted, etc.)
- Check
viewContext.isDestroyed()after async operations - Use
viewContext.isViewingOldVersion()before allowing edits - Property formulas should return null for invalid inputs
Working with Records
Getting Records from a Collection
const collections = await this.data.getAllCollections();
const collection = collections[0];
const records = await collection.getAllRecords();
for (const record of records) {
const title = record.getName();
const status = record.prop("Status")?.text();
console.log(title, status);
}
Setting Properties
const record = this.data.getRecord(guid);
record.prop("Status").set("Done");
record.prop("Amount").set(42);
record.prop("Priority").setChoice("high");
Creating Records
const collection = (await this.data.getAllCollections())[0];
const newGuid = collection.createRecord("New Item Title");
const newRecord = this.data.getRecord(newGuid);
newRecord.prop("Status").set("Todo");