> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/sxyazi/yazi/llms.txt
> Use this file to discover all available pages before exploring further.

# PubSub API

> Publish/Subscribe messaging system for Yazi plugins

The `ps` namespace provides a publish/subscribe messaging system for communication between plugins and Yazi components.

## Overview

The PubSub system allows plugins to:

* Subscribe to events from Yazi (DDS events)
* Publish custom events to other plugins
* Communicate between plugin instances
* React to system events

## Subscription

### `ps.sub(kind, callback)`

Subscribe to a local event.

<ParamField path="kind" type="string" required>
  Event kind/name to subscribe to
</ParamField>

<ParamField path="callback" type="function" required>
  Callback function to handle events
</ParamField>

```lua theme={null}
ps.sub("my-event", function(body)
  ya.dbg("Received event:", body)
end)
```

<Warning>
  Calling `ps.sub()` twice for the same event kind will error. Each event can only have one subscriber per plugin.
</Warning>

### `ps.sub_remote(kind, callback)`

Subscribe to a remote event (from other Yazi instances or DDS).

<ParamField path="kind" type="string" required>
  Event kind to subscribe to
</ParamField>

<ParamField path="callback" type="function" required>
  Callback function
</ParamField>

```lua theme={null}
ps.sub_remote("cd", function(body)
  ya.dbg("Directory changed:", body.url)
end)
```

<Note>
  As of [#3638](https://github.com/sxyazi/yazi/pull/3638), `ps.sub()` can be used directly in `init.lua` without requiring a plugin.
</Note>

## Publishing

### `ps.pub(kind, value)`

Publish an event locally.

<ParamField path="kind" type="string" required>
  Event kind
</ParamField>

<ParamField path="value" type="any" required>
  Event data (must be serializable)
</ParamField>

```lua theme={null}
ps.pub("my-event", { message = "Hello", count = 42 })
```

### `ps.pub_to(receiver, kind, value)`

Publish an event to a specific receiver.

<ParamField path="receiver" type="Id" required>
  Receiver ID
</ParamField>

<ParamField path="kind" type="string" required>
  Event kind
</ParamField>

<ParamField path="value" type="any" required>
  Event data
</ParamField>

```lua theme={null}
local target_id = ya.id("app")
ps.pub_to(target_id, "custom-event", { data = "targeted" })
```

## Unsubscription

### `ps.unsub(kind)`

Unsubscribe from a local event.

<ParamField path="kind" type="string" required>
  Event kind to unsubscribe from
</ParamField>

<ResponseField name="return" type="bool">
  True if was subscribed, false otherwise
</ResponseField>

```lua theme={null}
if ps.unsub("my-event") then
  ya.dbg("Unsubscribed successfully")
end
```

### `ps.unsub_remote(kind)`

Unsubscribe from a remote event.

<ParamField path="kind" type="string" required>
  Event kind to unsubscribe from
</ParamField>

<ResponseField name="return" type="bool">
  True if was subscribed, false otherwise
</ResponseField>

```lua theme={null}
ps.unsub_remote("cd")
```

## DDS Events

Yazi emits various DDS (Data Distribution Service) events that plugins can subscribe to:

### File Events

* **`cd`** - Directory changed
  ```lua theme={null}
  ps.sub_remote("cd", function(body)
    ya.dbg("New dir:", body.url)
  end)
  ```

* **`duplicate`** - Files copied ([#3456](https://github.com/sxyazi/yazi/pull/3456))
  ```lua theme={null}
  ps.sub_remote("duplicate", function(body)
    -- Handle file copy
  end)
  ```

* **`download`** - Remote files downloaded ([#3687](https://github.com/sxyazi/yazi/pull/3687))
  ```lua theme={null}
  ps.sub_remote("download", function(body)
    ya.dbg("Downloaded:", body.urls)
  end)
  ```

### UI Events

* **`ind-app-title`** - Customize app title ([#3684](https://github.com/sxyazi/yazi/pull/3684))
  ```lua theme={null}
  ps.sub_remote("ind-app-title", function()
    return "Custom Title - " .. tostring(cx.active.current.cwd)
  end)
  ```

* **`ind-which-activate`** - Change which-key behavior ([#3608](https://github.com/sxyazi/yazi/pull/3608))
  ```lua theme={null}
  ps.sub_remote("ind-which-activate", function(body)
    -- Customize which-key display
  end)
  ```

* **`ind-sort`** - Change sorting in Lua ([#3391](https://github.com/sxyazi/yazi/pull/3391))
  ```lua theme={null}
  ps.sub_remote("ind-sort", function(files, sort_by)
    -- Custom sorting logic
    return sorted_files
  end)
  ```

### Key Events

* **`key-*`** - Allow changing or canceling key events ([#3005](https://github.com/sxyazi/yazi/pull/3005), [#3037](https://github.com/sxyazi/yazi/pull/3037))
  ```lua theme={null}
  ps.sub_remote("key-enter", function(key)
    -- Intercept Enter key
    return { consume = true }
  end)
  ```

* **`key-sort`** - Sorting key events ([#3391](https://github.com/sxyazi/yazi/pull/3391))

### System Events

* **`relay-notify-push`** - Customize notification handler ([#3642](https://github.com/sxyazi/yazi/pull/3642))
  ```lua theme={null}
  ps.sub_remote("relay-notify-push", function(notification)
    -- Custom notification handling
  end)
  ```

* **`hey`** - Fires when static messages are restored from persistence ([#3725](https://github.com/sxyazi/yazi/pull/3725))
  ```lua theme={null}
  ps.sub_remote("hey", function()
    -- Handle message restoration
  end)
  ```

### Yank Events

* **`@yank`** - Files yanked (copied/cut)
  ```lua theme={null}
  ps.sub_remote("@yank", function(yanked)
    for url, _ in pairs(yanked) do
      ya.dbg("Yanked:", url)
    end
  end)
  ```

## Examples

### Subscribe to Directory Changes

```lua theme={null}
-- In init.lua
ps.sub("cd", function()
  local cwd = cx.active.current.cwd
  ya.dbg("Changed to: " .. tostring(cwd))
end)
```

### Custom Event Communication

```lua theme={null}
-- Plugin A: Publisher
function publish_status()
  ps.pub("status-update", {
    progress = 75,
    message = "Processing..."
  })
end

-- Plugin B: Subscriber
ps.sub("status-update", function(data)
  ya.notify {
    title = "Status",
    body = string.format("%s (%d%%)", data.message, data.progress),
  }
end)
```

### DDS Event Bridge (from preset)

The `dds.lua` preset plugin bridges DDS events to Yazi actions:

```lua theme={null}
ps.sub_remote("dds-emit", function(action)
  -- Convert DDS action to Yazi action
  local args = {}
  for i = 2, #action do
    local word = string.char(table.unpack(action[i]))
    local key = word:match("^%-%-([^=]+)")
    if key then
      args[key] = word:sub(#key + 4) or true
    else
      args[#args + 1] = word
    end
  end
  ya.emit(action[1], args)
end)
```

### Clean Up Subscriptions

```lua theme={null}
local cleanup = function()
  ps.unsub("my-event")
  ps.unsub_remote("cd")
end

-- Call cleanup when plugin is done
return {
  setup = function()
    ps.sub("my-event", handler)
  end,
  cleanup = cleanup,
}
```

## Best Practices

1. **Subscribe early** - Set up subscriptions in `init.lua` or plugin setup function
2. **Handle errors** - Wrap callback logic in pcall to prevent crashes
3. **Unsubscribe** - Clean up subscriptions when no longer needed
4. **Serialize data** - Ensure published data is JSON-serializable
5. **Avoid loops** - Be careful not to create event loops (A publishes → B subscribes and publishes → A subscribes...)

## See Also

* [DDS Documentation](https://yazi-rs.github.io/docs/dds) - Full DDS event reference
* [ya.emit()](ya#emit) - Emit custom actions
* [Plugin Examples](https://github.com/sxyazi/yazi/tree/main/yazi-plugin/preset/plugins) - Preset plugins using PubSub
