Build Thymer plugins - use when the user asks to create, modify, or debug Thymer plugins for the note-taking/project management app
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.
Extends the entire Thymer application:
Extends a specific note Collection:
plugin.js # Plugin code (extends AppPlugin or CollectionPlugin)
plugin.json # Configuration (name, icon, fields, views)
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.)
}
}
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)
});
}
}
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 typesgetAllRecords() - Get all workspace recordsgetRecord(guid) - Get record by GUIDcreateNewRecord(title) - Create recordgetAllCollections() - Get all collectionsgetActiveUsers() - Get workspace usersformula(name, fn) - Computed propertyrender(name, fn) - Custom property renderingcustomSort(name, fn) - Custom sortingregister(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 hookgetName() - 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 contentnumber() - Get as numbertext() - Get as textdate() - Get as Datechoice() - Get choice IDset(value) - Set valuesetChoice(choiceName) - Set by choice name{
"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"]
}
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-computedtable - Table viewboard - Kanban boardgallery - Card gallerycalendar - Calendar viewcustom - Fully custom viewrecord - Single record viewCommon 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.
plugin.js and plugin.jsonnpm run dev for hot reloadnpm run buildonUnload() (intervals, listeners)viewContext.isDestroyed() after async operationsviewContext.isViewingOldVersion() before allowing editsconst 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);
}
const record = this.data.getRecord(guid);
record.prop("Status").set("Done");
record.prop("Amount").set(42);
record.prop("Priority").setChoice("high");
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");
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer