Claude Code Plugins

Community-maintained marketplace

Feedback
6
0

Build Thymer plugins - use when the user asks to create, modify, or debug Thymer plugins for the note-taking/project management app

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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

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 bar
  • addSidebarItem({label, icon, tooltip, onClick}) - Sidebar
  • addCommandPaletteCommand({label, icon, onSelected}) - Commands
  • addToaster({title, message, dismissible, autoDestroyTime}) - Notifications
  • createButton({icon, label, onClick}) - Create button element
  • createDropdown({attachedTo, options}) - Dropdown menu
  • injectCSS(cssString) - Add global styles
  • getActivePanel() - Get focused panel
  • getPanels() - Get all panels
  • createPanel() - Create new panel
  • registerCustomPanelType(id, callback) - Custom panel types

DataAPI (this.data)

  • getAllRecords() - Get all workspace records
  • getRecord(guid) - Get record by GUID
  • createNewRecord(title) - Create record
  • getAllCollections() - Get all collections
  • getActiveUsers() - Get workspace users

PropertiesAPI (this.properties) - CollectionPlugin only

  • formula(name, fn) - Computed property
  • render(name, fn) - Custom property rendering
  • customSort(name, fn) - Custom sorting

ViewsAPI (this.views) - CollectionPlugin only

  • register(viewName, createHooksFn) - Custom view type
  • afterRenderBoardCard(viewName, fn) - Board card hook
  • afterRenderBoardColumn(viewName, fn) - Board column hook
  • afterRenderGalleryCard(viewName, fn) - Gallery card hook
  • afterRenderTableCell(viewName, fn) - Table cell hook
  • afterRenderCalendarEvent(viewName, fn) - Calendar event hook

PluginRecord

  • getName() - Record title
  • guid - Record GUID
  • prop(name) - Get property by name
  • number(name) - Get numeric property
  • text(name) - Get text property
  • date(name) - Get date property
  • getProperties(viewName) - Get visible properties
  • getLineItems() - Get document content

PluginProperty

  • number() - Get as number
  • text() - Get as text
  • date() - Get as Date
  • choice() - Get choice ID
  • set(value) - Set value
  • setChoice(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 text
  • number - Numeric (formats: plain, formatted, USD, EUR, etc.)
  • choice - Enum/status with choices
  • datetime - Date/time
  • user - User reference
  • record - Record reference
  • url - URL link
  • file - File attachment
  • image - Image
  • dynamic - Formula-computed

View Types

  • table - Table view
  • board - Kanban board
  • gallery - Card gallery
  • calendar - Calendar view
  • custom - Fully custom view
  • record - 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

  1. Edit plugin.js and plugin.json
  2. Run npm run dev for hot reload
  3. Build with npm run build
  4. 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");