Custom Modules
This special module type lets you extend the functionality of Ashell by creating your own simple components.
A custom module allows you to:
- Display the output of a command (live updates from a continuously running process).
- Run a command when the module is clicked.
- Change icons dynamically based on output.
- Show an alert indicator based on specific conditions.
- Show a tooltip on hover.
Ashell comes with a set of default icons that are used internally.
If you specify a font icon in the custom module configuration remember to install the font with that icon on your system.
For example you can use Nerd Fonts
Configuration
To define a custom module, use the following fields:
name: Name of the module. Use this to refer to it in the modules definitions.type(optional): Display type. Can beButton(clickable, default) orText(display only).icon(optional): Icon displayed in the status bar (Buttononly).command(optional): Command to execute when the module is clicked (Buttononly). Empty or whitespace-only values are treated as unset.on_right_click(optional): Command to execute on right-click (Buttononly).on_middle_click(optional): Command to execute on middle-click (Buttononly).on_scroll_up(optional): Command to execute when scrolling up over the module (Buttononly).on_scroll_down(optional): Command to execute when scrolling down over the module (Buttononly).listen_cmd(optional): Command to run in the background to update the module's display. Empty or whitespace-only values are treated as unset.icons(optional): Regex-to-icon mapping matched againstaltfrom thelisten_cmdoutput (Buttononly). The first matching regex wins; since the mappings are stored as a map, the evaluation order is not guaranteed. Prefer mutually exclusive regexes or keep patterns precise to avoid ambiguous matches.alert(optional): Regex matched againstaltto trigger a red alert dot on the icon (Buttononly).
A Text module has no interactions at all: command, on_right_click,
on_middle_click, on_scroll_up and on_scroll_down are ignored, and so are
icon, icons and alert. It renders only the text field coming from
listen_cmd, and renders nothing when that field is absent or empty. The
tooltip field still works on a Text module.
icons and alert match against alt, not against text.
listen_cmd
The listen_cmd is started once on startup and should continuously run in the background,
outputting JSON whenever the module's display should be updated. The most recent output
is used to update the module's display.
:::tip How listen_cmd Works
The listen_cmd is not executed periodically. Instead, it runs as a long-lived process
that outputs JSON whenever the displayed information changes. This is ideal for:
- Status monitoring: Commands that watch for changes and output when they occur
- Event listeners: Commands that wait for specific events and report them
- Live data: Commands that continuously stream status updates
For example, a notification listener might output JSON only when new notifications arrive, rather than polling every second.
:::
The listen_cmd should output JSON in
the Waybar format,
using the text, alt and tooltip fields.
alt is required. A payload without it fails to parse, the error is logged
and the update is dropped, so emit "alt": "" even when you have nothing to
match on. text and tooltip are optional; only text, alt and tooltip are read,
any other Waybar field (class, percentage, tooltip-format) is ignored.
:::tip JSON Output
Output compact single-line JSON:
{"text": "3", "alt": "notification"}
If you have pretty-printed JSON and want to use it in a single line, pipe it through jq to compact it:
your-command | jq -c --unbuffered .
For example:
echo '{
"text": "3",
"alt": "notification"
}' | jq -c --unbuffered .
# Output: {"text":"3","alt":"notification"}
Or you can output pretty-printed multiline JSON directly, which is buffered until valid JSON is formed:
{
"text": "3",
"alt": "notification"
}
:::warning Multiline JSON Buffer Limit
When using pretty-printed (multiline) JSON, output is accumulated in an internal buffer until a complete, parseable JSON object is received. As a safeguard against malformed output, for example a script that opens a { and never closes it, the buffer is capped at 1 MiB. If that limit is exceeded, the buffered bytes are dropped, a warning is logged, and the buffer is cleared.
This limit only applies while buffering multiline JSON. Single-line (compact) JSON output is unaffected since each line is parsed independently.
:::
See the configuration examples below for multiline usage.
:::
Example Output
{"text": "3", "alt": "notification"}
Tooltip
When the payload has a non-empty tooltip, hovering over the module shows it in
a popup. Use \n to split it over several lines:
{"text": "11:00 Standup", "alt": "", "tooltip": "11:00 Standup\n14:00 Review"}
To remove the tooltip, emit a payload without it or with an empty string; an open tooltip closes right away.
The tooltip is plain text: Waybar's Pango markup (<b>, <span>, ...) is
shown as is. It is not affected by enable_tooltips in the
settings module, which only covers the settings
indicators.
Dynamic Icons
You can change the icon depending on the value of alt in the listen_cmd output.
Icons Example
icons.'dnd.*' = ""
This will change the icon to when alt matches dnd.*.
Alerts
Use the alert field to show a red dot on the module icon if the output
matches a given regex.
Alerts Example
alert = ".*notification"
Examples
Text-only Module (e.g., Custom Clock)
Text modules display only the text output from listen_cmd without any click action:
[[CustomModule]]
name = "MyClock"
type = "Text"
listen_cmd = '''
while true; do
cat <<EOF
{
"text": "$(date +'%H:%M')",
"alt": ""
}
EOF
sleep 1
done
'''
Button Module with Icon (Interactive)
Button modules display an icon and/or text with a click action. They also support right-click, middle-click, and scroll events:
[[CustomModule]]
name = "volume"
type = "Button"
icon = ""
command = "pactl get-sink-volume @DEFAULT_SINK@"
on_right_click = "pactl set-sink-mute @DEFAULT_SINK@ toggle"
on_middle_click = "pavucontrol"
on_scroll_up = "pactl set-sink-volume @DEFAULT_SINK@ +5%"
on_scroll_down = "pactl set-sink-volume @DEFAULT_SINK@ -5%"
Button Module with Icon Only
[[CustomModule]]
name = "AppLauncher"
type = "Button"
icon = ""
command = "walker"
Button Module with Text Output
Button modules can display text from a multiline JSON output from listen_cmd with a click action:
[[CustomModule]]
name = "Clipboard"
type = "Button"
icon = "📋"
command = "cliphist-rofi-img | wl-copy"
listen_cmd = '''printf '%s\n' '{
"text": "Clipboard content",
"alt": ""
}'
'''
Notifications (with wired)
[[CustomModule]]
name = "CustomNotifications"
type = "Button"
icon = ""
command = "wired --show 5"
listen_cmd = "wired count"
icons.'dnd.*' = ""
alert = ".*notification"
[[CustomModule]]
name = "CustomNotifications"
icon = ""
command = "swaync-client -t -sw"
listen_cmd = "swaync-client -swb"
icons.'dnd.*' = ""
alert = ".*notification"
App Launcher (with walker)
[[CustomModule]]
name = "AppLauncher"
icon = ""
command = "walker"
Clipboard (with cliphist)
[[CustomModule]]
name = "Clipboard"
icon = "📋"
command = "cliphist-rofi-img | wl-copy"
Migration from Deprecated Modules
The AppLauncher and Clipboard modules have been replaced by custom modules.
The old app_launcher_cmd and clipboard_cmd keys no longer exist: ashell
ignores them and logs Unknown configuration field ignored: app_launcher_cmd
at startup (also printed to stderr). To migrate:
Previous App Launcher Configuration
# Old deprecated way
app_launcher_cmd = "walker"
New Custom Module Configuration
# New recommended way
[[CustomModule]]
name = "AppLauncher"
icon = ""
command = "walker"
Previous Clipboard Configuration
# Old deprecated way
clipboard_cmd = "cliphist-rofi-img | wl-copy"
New Custom Module Configuration
# New recommended way
[[CustomModule]]
name = "Clipboard"
icon = ""
command = "cliphist-rofi-img | wl-copy"
Then add the custom modules to your modules configuration:
[modules]
right = [ [ "Tempo", "Privacy", "Settings", "AppLauncher", "Clipboard" ] ]