Skip to content

custom

User-defined module that runs a shell command and renders the output in the bar.

Full walkthrough with examples at https://wayle.app/guide/custom-modules.

Add it to your layout with custom-<id>:

toml
[[bar.layout]]
monitor = "*"
right = ["custom-<id>"]

General

FieldTypeDefaultDescription
idstringrequiredUnique identifier for this module.
commandunknownnullShell command to execute.
modeExecutionMode"poll"Execution mode for the command.
interval-msu645000Polling interval in milliseconds.
formatstring"{{ output }}"Format string for the label using Jinja2 template syntax.
tooltip-formatunknownnullFormat string for the tooltip (hover text).
hide-if-emptyboolfalseHide module when output is empty, "0", or "false".
class-formatunknownnullFormat string for dynamic CSS classes.
label-showbooltrueDisplay text label.
label-max-lengthu320Maximum label length in characters before truncation.
border-showboolfalseDisplay border around button.
left-clickstring""Shell command executed on left click.
right-clickstring""Shell command executed on right click.
middle-clickstring""Shell command executed on middle click.
scroll-upstring""Shell command executed on scroll up.
scroll-downstring""Shell command executed on scroll down.
on-actionunknownnullShell command to run after any click/scroll action completes.
More about id

Referenced in bar layouts as custom-<id>. Must be unique across all custom module definitions.

Example

toml
[[modules.custom]]
id = "gpu-temp"

# Reference in layout:
# layout = ["custom-gpu-temp", "clock"]
More about command

The command runs via sh -c and should output to stdout. Stderr is discarded. Commands have a 30-second timeout.

Output Parsing

  • If output starts with { or [: parsed as JSON
  • Otherwise: treated as plain text

Behavior by Mode

  • poll: Executed every interval-ms milliseconds
  • watch: Spawned once, each stdout line triggers a display update. Restarts are controlled by restart-policy.
More about mode
ModeBehavior
pollRun command every interval-ms (default)
watchSpawn long-running process, update on each stdout line

Use poll for commands that return current state and exit. Use watch for commands that stream updates (e.g., pactl subscribe).

More about interval-ms

Only applies to poll mode. Ignored in watch mode.

Set to 0 for manual polling mode: no timer is started. In manual mode, the command still runs once at startup.

More about format

Variables

  • {{ output }} - Raw command output
  • {{ field }} - JSON field access
  • {{ nested.field }} - Nested field access
  • {{ items.0 }} - Array index access

Filters

  • {{ val | default('fallback') }} - Fallback for missing values
  • {{ "%02d" | format(val) }} - Zero-padding
  • {{ val | upper }}, | lower, | trim - String transforms

Examples

  • "{{ output }}°C" - Plain text: "72°C"
  • "{{ percentage }}%" - JSON field: "75%"
  • "{{ data.temp }}°C" - Nested: "22°C"

If JSON output contains a text field, it overrides this format.

More about tooltip-format

Supports the same Jinja2 syntax as format. If not set, no tooltip is shown. If JSON output contains a tooltip field, it overrides this format.

Example

toml
format = "{{ percentage }}%"
tooltip-format = "Volume: {{ percentage }}% on {{ device }}"
More about hide-if-empty

When enabled, the module (including its gap in the bar layout) is completely hidden if the output indicates an empty/disabled state.

More about class-format

Supports the same Jinja2 syntax as format. The formatted result is split on whitespace and each word is added as a CSS class.

Combined with the class field from JSON output (if present).

Example

toml
class-format = "volume-{{ alt }}"
# Output: {"alt": "muted"} → adds class "volume-muted"
More about label-max-length

When exceeded, label is truncated with ellipsis. Set to 0 to disable.

More about left-click

If on-action is set, it runs after this command completes.

More about right-click

If on-action is set, it runs after this command completes.

More about middle-click

If on-action is set, it runs after this command completes.

More about scroll-up

Scroll events are debounced (50ms) to coalesce rapid scrolls. If on-action is set, it runs after this command completes.

More about scroll-down

Scroll events are debounced (50ms) to coalesce rapid scrolls. If on-action is set, it runs after this command completes.

More about on-action

Executes after the action handler finishes, and its output updates the display immediately. Useful for reflecting state changes without waiting for the next poll interval.

Example

toml
# Volume control with immediate feedback
scroll-up = "pactl set-sink-volume @DEFAULT_SINK@ +5%"
scroll-down = "pactl set-sink-volume @DEFAULT_SINK@ -5%"
on-action = '''
vol=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -oP '\d+(?=%)' | head -1)
echo "{\"percentage\": $vol}"
'''

Colors

FieldTypeDefaultDescription
icon-colorColorValue"auto"Icon foreground color.
icon-bg-colorColorValue"auto"Icon container background color.
label-colorColorValue"auto"Label text color.
button-bg-colorColorValue"bg-surface-elevated"Button background color.
border-colorColorValue"auto"Border color.

Icons

FieldTypeDefaultDescription
icon-namestring""Static symbolic icon name.
icon-namesunknownnullArray of icon names indexed by percentage (0-100).
icon-mapunknownnullMap of icon names keyed by the alt field value.
icon-showbooltrueDisplay module icon.
More about icon-name

Used when icon-names and icon-map don't provide a match. Should be a symbolic icon name from the icon theme (e.g., "ld-gpu-symbolic").

Example

toml
icon-name = "ld-temperature-symbolic"
More about icon-names

Requires JSON output with a percentage field (0-100). The array is divided evenly across the percentage range.

Resolution

For N icons, icon at index floor(percentage * N / 101) is selected:

  • 4 icons: 0-24% → [0], 25-49% → [1], 50-74% → [2], 75-100% → [3]
  • 5 icons: 0-19% → [0], 20-39% → [1], 40-59% → [2], 60-79% → [3], 80-100% → [4]

Example

toml
icon-names = [
  "battery-empty-symbolic",
  "battery-caution-symbolic",
  "battery-low-symbolic",
  "battery-good-symbolic",
  "battery-full-symbolic"
]
More about icon-map

Requires JSON output with an alt field. The alt value is looked up in this map. Use "default" as a fallback key.

Priority: icon-map[alt] takes precedence over icon-names[percentage], allowing state-specific icons to override percentage-based icons.

Example

toml
# Volume with muted state override
icon-names = ["vol-0", "vol-33", "vol-66", "vol-100"]
icon-map = { "muted" = "audio-volume-muted-symbolic" }

# Output: {"percentage": 50, "alt": "muted"}
# Result: Uses "audio-volume-muted-symbolic" (alt match beats percentage)

# Output: {"percentage": 50}
# Result: Uses "vol-33" (percentage-based, no alt)

Restart

FieldTypeDefaultDescription
restart-policyRestartPolicy"never"Restart policy for watch mode.
restart-interval-msRestartDelay1000Base restart delay in milliseconds for watch mode.
More about restart-policy

Only applies to watch mode. Ignored in poll mode.

PolicyBehavior
neverDo not restart after exit
on-exitRestart after any exit
on-failureRestart only after non-zero/signal exit
More about restart-interval-ms

Only applies to watch mode. Ignored in poll mode.

Used when restart-policy is on-exit or on-failure. Delay increases exponentially on rapid failures, capped at 30 seconds.

Default configuration

Required fields (must be set in your config): id.

toml
[[modules.custom]]
mode = "poll"
interval-ms = 5000
restart-policy = "never"
restart-interval-ms = 1000
format = "{{ output }}"
hide-if-empty = false
icon-name = ""
icon-show = true
icon-color = "auto"
icon-bg-color = "auto"
label-show = true
label-color = "auto"
label-max-length = 0
button-bg-color = "bg-surface-elevated"
border-show = false
border-color = "auto"
left-click = ""
right-click = ""
middle-click = ""
scroll-up = ""
scroll-down = ""

Released under the MIT License.