Quicky Notes features a powerful, embedded Rhai Scripting Plugin Engine. Plugins allow you to customize and extend Quicky Notes with custom header buttons, global hotkeys, right-click context menu tools, note buffer transformations, clipboard automations, persistent storage, pure-Rust HTTP REST requests, recurring timers, dynamic theme color overrides, collapsible bottom console panels, and native terminal launchers.
- Quick Installation & Setup
- Managing Plugins in Settings
- Plugin Architecture & Lifecycle Hooks
- Host API Reference
- Complete Practical Examples
- Security & Sandboxing Quotas
All user plugin scripts reside in your standard config folder:
~/.config/quicky_notes/plugins/
To install any plugin:
- Save your script file with a
.rhaiextension (e.g.pomodoro_timer.rhai) directly inside~/.config/quicky_notes/plugins/. - Or create a subdirectory containing
plugin.rhaiormain.rhai:~/.config/quicky_notes/plugins/ ├── pomodoro_timer.rhai ├── quote_of_the_day.rhai ├── quick_terminal.rhai └── my_custom_tool/ └── plugin.rhai - Open Quicky Notes (or click 🔄 Reload in Settings) — your plugin is compiled and active immediately!
Open Settings & Preferences by clicking the gear icon or pressing Ctrl + ,, then switch to the 🔌 Plugins tab:
- Master Toggle (
Enable Rhai Plugin System): Enables or disables the entire plugin runtime. - 📁 Open Folder: Opens
~/.config/quicky_notes/plugins/in your desktop file manager. - 🔄 Reload: Re-scans the directory and hot-reloads all scripts in memory without restarting.
- ✨ Starter Plugins: Generates official starter templates.
- Plugin Cards:
- Enable or disable individual plugins with dedicated toggle switches.
- View author, version, and capability badges (registered buttons, shortcuts, timers, and context menu items).
- Inspect compilation warnings and runtime error diagnostics.
Plugins are written in Rhai, an embedded scripting language designed specifically for Rust.
Every plugin can implement any combination of the following entrypoints:
| Hook | Parameters | Description |
|---|---|---|
init(plugin) |
plugin |
Registration entrypoint called once on load to configure metadata, header buttons, shortcuts, timers, and context menu entries. |
on_header_click(button_id, note, ui, system) |
button_id, note, ui, system |
Triggered when the user clicks a custom header bar icon registered by this plugin. |
on_shortcut(action_id, note, ui, system) |
action_id, note, ui, system |
Triggered when a registered keyboard shortcut combination is pressed. |
on_context_menu_click(action_id, note, ui, system) |
action_id, note, ui, system |
Triggered when a custom right-click context menu item is selected in the editor. |
on_timer(timer_id, note, ui, system) |
timer_id, note, ui, system |
Triggered periodically when a background timer interval elapses. |
on_save(note, ui, system) |
note, ui, system |
Triggered whenever the active note is saved to disk or SQLite. |
on_note_change(note, ui, system) |
note, ui, system |
Triggered whenever note text is modified. |
Note
All hook functions have global access to storage, http, theme, note, ui, and system inside their scope.
| Method / Property | Description |
|---|---|
plugin.name = "My Plugin" |
Sets the display name of the plugin. |
plugin.author = "Your Name" |
Sets the author string. |
plugin.version = "1.0.0" |
Sets the semantic version string. |
plugin.description = "..." |
Sets a description shown in the Plugin Manager. |
plugin.add_header_button(id, icon, tooltip, position) |
Registers a header icon button. position can be "right" or "left". |
plugin.add_shortcut(action_id, key_comb) |
Registers a global hotkey (e.g. "Ctrl+Shift+T", "Ctrl+\``", "Alt+F12"`). |
plugin.add_shortcut_with_label(action_id, key_comb, label) |
Registers a shortcut with a custom display label in Settings. |
plugin.add_context_menu_item(action_id, label) |
Registers an item in the right-click editor context menu. |
plugin.add_context_menu_item_with_icon(action_id, label, icon) |
Registers an item in the right-click menu with an icon prefix (e.g. ">_", "▦"). |
plugin.add_timer(timer_id, interval_seconds) |
Registers a recurring background timer in seconds (e.g. plugin.add_timer("tick", 1)). |
| Method | Return Type | Description |
|---|---|---|
note.get_text() |
String |
Returns the entire text content of the active note. |
note.set_text(text) |
() |
Replaces the entire content of the active note. |
note.get_selection() |
String |
Returns the currently selected text in the editor (or empty string if none). |
note.replace_selection(text) |
() |
Replaces the active selection range with the provided text. |
note.insert_at_cursor(text) |
() |
Inserts text at the current cursor position. |
note.get_title() |
String |
Returns the note title. |
note.set_title(title) |
() |
Sets the note title. |
note.get_file_path() |
String |
Returns the external disk file path if this note is linked to a file. |
note.get_id() |
String |
Returns the UUID of the active note. |
note.get_line_count() |
i64 |
Returns total line count. |
note.get_word_count() |
i64 |
Returns total word count. |
note.is_markdown() |
bool |
Returns true if Markdown syntax highlighting/preview is active. |
| Method | Description |
|---|---|
ui.toast(message) |
Displays a modern floating info toast notification. |
ui.toast_success(message) |
Displays a green success toast notification. |
ui.toast_error(message) |
Displays a red error toast notification. |
ui.toast_warning(message) |
Displays an amber warning toast notification. |
ui.set_status(message) |
Updates the bottom status bar message. |
ui.copy_to_clipboard(text) |
Writes text to the system clipboard. |
ui.get_clipboard() |
Reads the current text from the system clipboard. |
ui.show_panel(title, content) |
Opens the bottom output console drawer with the given title and text. |
ui.append_panel(text) |
Appends text to the active bottom output console drawer. |
ui.set_panel_content(content) |
Replaces the content in the active bottom console drawer. |
ui.clear_panel() |
Clears the active bottom console drawer. |
ui.hide_panel() |
Closes and hides the bottom console drawer. |
ui.request_repaint() |
Forces an immediate egui frame redraw. |
Each plugin gets its own isolated JSON data store saved automatically under ~/.config/quicky_notes/plugins_data/<plugin_id>.json:
| Method | Return Type | Description |
|---|---|---|
storage.get(key) |
String |
Gets a value by key (or empty string if not found). |
storage.set(key, value) |
() |
Sets a key-value pair and persists immediately to disk. |
storage.has(key) |
bool |
Checks if a key exists in storage. |
storage.delete(key) |
bool |
Deletes a key from storage. Returns true if the key was present. |
storage.clear() |
() |
Clears all stored data for this plugin. |
storage.keys() |
Array |
Returns an array of all keys stored for this plugin. |
storage.all() |
Map |
Returns a key-value dictionary of all stored data. |
Pure-Rust synchronous and background-ready HTTP client with 10s timeouts and a 5 MB response cap:
| Method | Return Type | Description |
|---|---|---|
http.get(url) |
String |
Performs an HTTP GET request and returns the response body. |
http.get_with_headers(url, headers_map) |
String |
Performs an HTTP GET request with custom headers dictionary. |
http.post(url, body) |
String |
Performs an HTTP POST request with a raw text/JSON body. |
http.post_json(url, body, headers_map) |
String |
Performs an HTTP POST request with custom headers. |
http.status_code() |
i64 |
Returns the HTTP status code of the last request (e.g. 200, 404). |
http.last_error() |
String |
Returns the last HTTP error message, if any. |
| Method | Return Type | Description |
|---|---|---|
theme.get_accent() |
String |
Returns the current accent color as a HEX string (e.g. "#7AA2F7"). |
theme.get_bg() |
String |
Returns the current background color as a HEX string. |
theme.set_accent(hex_color) |
() |
Dynamically sets a temporary accent color override (e.g. "#00F0FF"). |
theme.reset_accent() |
() |
Resets the accent color back to the active theme palette. |
theme.is_dark() |
bool |
Returns true if the active theme is a dark theme. |
| Method | Return Type | Description |
|---|---|---|
system.launch_terminal(dir) |
bool |
Auto-detects installed modern terminal emulators ($TERMINAL, kitty, alacritty, foot, ghostty, wezterm, gnome-terminal, konsole, xfce4-terminal, xterm) and spawns a window in dir. |
system.exec(command, args) |
bool |
Spawns a non-blocking background child process (e.g. system.exec("notify-send", ["Hi"])). |
system.exec_sync(command, args) |
String |
Executes a command synchronously and returns captured stdout (capped at 1 MB). |
system.open_folder(path) |
() |
Safely opens the directory in the desktop default file manager (xdg-open). |
system.open_url(url) |
() |
Opens an http:// or https:// URL in the default web browser. |
system.home_dir() |
String |
Returns the user's home directory path (/home/username). |
system.parent_dir(path) |
String |
Returns the parent directory containing the given file path. |
File: ~/.config/quicky_notes/plugins/pomodoro_timer.rhai
fn init(plugin) {
plugin.name = "Pomodoro Timer";
plugin.author = "Community";
plugin.version = "1.0.0";
plugin.description = "25-minute focus intervals and 5-minute breaks with timers and console drawer";
plugin.add_header_button("pomo_start", "🍅", "Toggle Pomodoro Timer", "right");
plugin.add_header_button("pomo_panel", "⏱️", "Toggle Pomodoro Output Panel", "right");
plugin.add_shortcut_with_label("pomo_toggle", "Ctrl+Alt+P", "Toggle Pomodoro Timer");
plugin.add_context_menu_item_with_icon("pomo_reset", "Reset Pomodoro Timer", "🔄");
// 1-second recurring background interval
plugin.add_timer("pomo_tick", 1);
return plugin;
}
fn on_header_click(button_id, note, ui, system) {
if button_id == "pomo_start" { toggle_timer(ui); }
else if button_id == "pomo_panel" { toggle_panel(ui); }
}
fn on_timer(timer_id, note, ui, system) {
if timer_id != "pomo_tick" || storage.get("running") != "true" { return; }
let remaining = storage.get("remaining").to_int();
if remaining > 0 {
remaining -= 1;
storage.set("remaining", `${remaining}`);
let mode = storage.get("mode");
let mins = remaining / 60;
let secs = remaining % 60;
let sec_str = if secs < 10 { `0${secs}` } else { `${secs}` };
ui.set_status(`🍅 [${mode}]: ${mins}:${sec_str}`);
if storage.get("panel_open") == "true" {
ui.set_panel_content(`=== Pomodoro Status ===\nPhase: ${mode}\nTime Left: ${mins}:${sec_str}\nStatus: Active Focusing`);
}
} else {
let mode = storage.get("mode");
if mode == "Work" {
storage.set("mode", "Break");
storage.set("remaining", "300");
ui.toast_success("🎉 Work session completed! Take a 5-minute break.");
system.exec("notify-send", ["Pomodoro", "Work session done! 5m break."]);
} else {
storage.set("mode", "Work");
storage.set("remaining", "1500");
ui.toast_success("🔔 Break finished! Ready to focus?");
system.exec("notify-send", ["Pomodoro", "Break over! Back to work."]);
}
}
}
fn toggle_timer(ui) {
let is_running = storage.get("running") == "true";
if is_running {
storage.set("running", "false");
ui.toast_warning("⏸️ Pomodoro timer paused.");
ui.set_status("Pomodoro: Paused");
} else {
if !storage.has("mode") {
storage.set("mode", "Work");
storage.set("remaining", "1500");
}
storage.set("running", "true");
ui.toast_success("🍅 Pomodoro timer started (25m focus)!");
}
}
fn toggle_panel(ui) {
let is_open = storage.get("panel_open") == "true";
if is_open {
storage.set("panel_open", "false");
ui.hide_panel();
} else {
storage.set("panel_open", "true");
ui.show_panel("Pomodoro Focus Console", "=== Pomodoro Status ===\nPhase: Work\nTime Left: 25:00");
}
}File: ~/.config/quicky_notes/plugins/quote_of_the_day.rhai
fn init(plugin) {
plugin.name = "Daily Wisdom Quotes";
plugin.author = "Community";
plugin.version = "1.0.0";
plugin.description = "Fetches programming wisdom over HTTP with console panel output";
plugin.add_header_button("fetch_quote", "💡", "Fetch Daily Programming Quote", "right");
plugin.add_context_menu_item_with_icon("quote_insert", "Insert Programming Quote", "📝");
return plugin;
}
fn on_header_click(button_id, note, ui, system) {
if button_id == "fetch_quote" {
ui.toast("Fetching quote over HTTP...");
let body = http.get("https://dummyjson.com/quotes/random");
if http.status_code() == 200 && !body.is_empty() {
ui.show_panel("Daily Programming Wisdom", `💡 Developer Wisdom\n--------------------------------\n\n"${body}"`);
ui.copy_to_clipboard(body);
ui.toast_success("Quote loaded & copied!");
} else {
ui.toast_error("Failed to retrieve quote over HTTP.");
}
}
}File: ~/.config/quicky_notes/plugins/custom_theme_cycler.rhai
fn init(plugin) {
plugin.name = "Theme Accent Cycler";
plugin.author = "Community";
plugin.version = "1.0.0";
plugin.description = "Cycles UI accent highlights dynamically between Cyberpunk Cyan, Tokyo Purple, and Emerald";
plugin.add_header_button("theme_cycle", "🎨", "Cycle Theme Accent Color", "left");
plugin.add_shortcut_with_label("theme_cycle_key", "Ctrl+Alt+T", "Cycle Accent Color");
return plugin;
}
fn on_header_click(button_id, note, ui, system) {
if button_id == "theme_cycle" {
let colors = ["#00F0FF", "#BB9AF7", "#2ECC71", "#FF007F", "#F59E0B"];
let idx = if storage.has("idx") { storage.get("idx").to_int() } else { 0 };
idx = (idx + 1) % colors.len();
storage.set("idx", `${idx}`);
theme.set_accent(colors[idx]);
ui.toast_success(`🎨 Accent color updated to ${colors[idx]}`);
}
}File: ~/.config/quicky_notes/plugins/quick_terminal.rhai
fn init(plugin) {
plugin.name = "Quick Terminal";
plugin.author = "amitxd";
plugin.version = "1.0.0";
plugin.description = "Auto-detects and launches terminal emulator in note directory";
plugin.add_header_button("btn_terminal", ">_", "Launch Terminal (Ctrl+`)", "right");
plugin.add_shortcut("open_terminal", "Ctrl+`");
plugin.add_context_menu_item_with_icon("open_terminal", "Open Terminal in Folder", ">_");
return plugin;
}
fn on_header_click(button_id, note, ui, system, storage, http, theme) {
if button_id == "btn_terminal" {
launch_term(note, ui, system);
}
}
fn on_shortcut(action_id, note, ui, system, storage, http, theme) {
if action_id == "open_terminal" {
launch_term(note, ui, system);
}
}
fn on_context_menu_click(action_id, note, ui, system, storage, http, theme) {
if action_id == "open_terminal" {
launch_term(note, ui, system);
}
}
fn launch_term(note, ui, system) {
let fp = note.get_file_path();
let target_dir = if fp != "" { system.parent_dir(fp) } else { system.home_dir() };
let launched = system.launch_terminal(target_dir);
if launched {
ui.toast_success("Terminal launched in " + target_dir);
} else {
ui.toast_error("Could not find an installed terminal emulator.");
}
}To guarantee responsive UI performance and prevent runaway resource consumption, the Rhai runtime enforces strict execution limits:
- Max Operations (
500,000steps): Prevents hanging script loops from blocking the UI thread. - Max Call Depth (
50frames): Protects against stack overflow from uncontrolled recursion. - Max String Size (
5 MB): Bounds heap allocations inside scripts. - Max HTTP Payload (
5 MB): Bounded streaming prevents oversized network responses from causing excessive memory usage. - HTTP Timeout (
10 Seconds): All network calls time out automatically. - Synchronous Process Timeout (
5 Seconds): All synchronous system commands time out and terminate automatically. - Path Traversal Protection: Folder opening, terminal spawning, and plugin persistent key-value storage enforce safe directory boundaries.
- Trust & Permissions: Plugins execute with user privileges when invoking host system APIs (
system.exec,system.exec_sync). Only install plugins from trusted sources.