Skip to content

Instantly share code, notes, and snippets.

@mdeguzis
Created April 29, 2026 22:44
Show Gist options
  • Select an option

  • Save mdeguzis/7bef2731edd67a6dea06ffc622a1bae6 to your computer and use it in GitHub Desktop.

Select an option

Save mdeguzis/7bef2731edd67a6dea06ffc622a1bae6 to your computer and use it in GitHub Desktop.
How to actually mute toast sounds in Decky Loader plugins

How to Actually Mute Toast Sounds in Decky Loader Plugins

playSound: false does not work. Here is why, and what does.

The Problem

Decky Loader's toaster.toast() accepts playSound?: boolean and sound?: number, but neither field silences the sound in practice.

What Decky actually sends to Steam

Decky's internal toast() function builds an info object like this:

const info = {
  showToast: toast.showToast,
  sound: toast.sound,       // your sound value IS forwarded
  eFeature: 0,
  toastDurationMS: ...,
  bCritical: toast.critical,
  fnTray,
  // playSound is NOT included here
};
window.NotificationStore.ProcessNotification(info, toastData, ToastType.New);

playSound is silently dropped.

Where the sound is actually played

When Steam renders the toast, the React component runs:

useEffect(() => { e && NotificationStore.PlayNotificationSound(e) }, [e])

PlayNotificationSound(toastData) does:

const r = W[toastData.eType];   // looks up Steam's hardcoded type map -- NOT your info object
const soundId = ChooseSound(r, toastData);
if (soundId) PlayNavSound(soundId);

ChooseSound logic:

const play = e.playSound ?? !!e.sound;
if ((clientSettings.play_sound_on_toast || e.bCritical) && play) return e.sound;
return null;

For the Decky default eType: 31 ("General"), W[31] has sound: ToastMisc and no playSound, so play = true -- sound always plays, regardless of what you put in your toast args.

The Fix

Pass eType: 40 when muting. W[40] ("Steam Input Action Set") has playSound: false:

// ChooseSound(W[40], ...) -> play = false -> returns null -> no sound

Decky still renders your toast normally because it uses its own info object (with showToast: true, your title/body). The eType only affects the PlayNotificationSound lookup.

In practice (TypeScript)

const soundOn = getSetting('toast-sound-enabled', true);

deckyToaster.toast({
  title: 'My Plugin',
  body: 'Hello!',
  playSound: soundOn,
  // sound:0 covers the info.sound path; eType:40 covers the W[eType] path
  ...(soundOn ? {} : { sound: 0, eType: 40 }),
});

Why Two Fields?

  • sound: 0 -- silences the ChooseSound(info, ...) call inside ProcessNotification (where info is used).
  • eType: 40 -- silences PlayNotificationSound(toastData) called by the toast React component (where W[eType] is used).

Both code paths are hit when a toast is shown; you need both to be safe.

Verified Against

  • Decky Loader: checked against /tmp/_MEI*/decky_loader/static/chunk-*.js
  • Steam UI: checked against /home/deck/.steam/root/steamui/chunk~2dcc5aaf7.js
  • SteamOS stable (April 2025)

eType Reference (Silent Options)

eType Steam Name playSound showToast Notes
40 Steam Input Action Set false true Best choice -- toast shows, no sound
41 Remote Client Connection false false Toast hidden in W but Decky info overrides
43 Streaming Client Connection false false Same
57 GRUM false false bEnableInReducedUI: false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment