> ## 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.

# Creating Plugins

> Step-by-step guide to building your own Yazi plugins

Learn how to create custom plugins to extend Yazi's functionality using Lua.

## Plugin Basics

Yazi plugins are written in Lua and follow a simple structure. Each plugin is a Lua module that returns a table with functions.

### Minimal Plugin Example

Here's the simplest possible plugin:

```lua theme={null}
-- ~/.config/yazi/plugins/hello.lua
local M = {}

function M:entry()
  ya.notify {
    title = "Hello Plugin",
    content = "Hello from Yazi!",
    timeout = 3,
    level = "info"
  }
end

return M
```

Call it from your keymap:

```toml theme={null}
[[manager.prepend_keymap]]
on = [ "h", "i" ]
run = "plugin hello"
desc = "Say hello"
```

## Plugin Structure

### Module Pattern

All plugins follow this pattern:

```lua theme={null}
local M = {}  -- Create module table

-- Add methods to module
function M:entry()
  -- Plugin logic here
end

return M  -- Return module
```

The `M` table can contain:

* Methods (functions)
* State variables
* Configuration options

### Entry Points

Different plugin types have different entry points:

| Plugin Type  | Entry Point              | Purpose                 |
| ------------ | ------------------------ | ----------------------- |
| Functional   | `entry(job)`             | Main execution function |
| Previewer    | `peek(job)`, `seek(job)` | Preview generation      |
| Fetcher      | `fetch(job)`             | Metadata retrieval      |
| Spotter      | `spot(job)`              | Info panel display      |
| UI Component | `redraw()`               | UI rendering            |

## Step-by-Step Plugin Creation

Let's create a plugin that counts files in the current directory.

<Steps>
  <Step title="Create the plugin file">
    Create `~/.config/yazi/plugins/filecount.lua`:

    ```lua theme={null}
    local M = {}

    function M:entry()
      -- We'll add logic here
    end

    return M
    ```
  </Step>

  <Step title="Access current directory">
    Use the `cx` (context) global to access Yazi's state:

    ```lua theme={null}
    function M:entry()
      local cwd = cx.active.current.cwd
      local files = cx.active.current.files
      
      ya.notify {
        title = "File Count",
        content = string.format("%d files in %s", #files, tostring(cwd)),
        timeout = 3
      }
    end
    ```
  </Step>

  <Step title="Add filtering logic">
    Count different file types:

    ```lua theme={null}
    function M:entry()
      local files = cx.active.current.files
      local dirs, regulars, hidden = 0, 0, 0
      
      for _, file in ipairs(files) do
        if file.cha.is_dir then
          dirs = dirs + 1
        else
          regulars = regulars + 1
        end
        
        if file.name:sub(1, 1) == "." then
          hidden = hidden + 1
        end
      end
      
      ya.notify {
        title = "File Count",
        content = string.format(
          "Total: %d\nDirectories: %d\nFiles: %d\nHidden: %d",
          #files, dirs, regulars, hidden
        ),
        timeout = 5
      }
    end
    ```
  </Step>

  <Step title="Bind to a key">
    Add to `keymap.toml`:

    ```toml theme={null}
    [[manager.prepend_keymap]]
    on = [ "c", "c" ]
    run = "plugin filecount"
    desc = "Count files in directory"
    ```
  </Step>
</Steps>

## Accessing Yazi State

Yazi provides global objects to access its state:

### `cx` - Context

The main state object:

```lua theme={null}
-- Current tab
local current = cx.active.current
local cwd = current.cwd        -- Current directory
local files = current.files    -- Files in current dir
local hovered = current.hovered -- Currently hovered file

-- Selection
local selected = cx.active.selected  -- Selected files

-- Yanked files (copy/cut)
local yanked = cx.yanked
local is_cut = cx.yanked.is_cut

-- Tab info
local mode = cx.active.mode    -- Select/unset/normal mode
```

### `rt` - Runtime

Configuration and runtime settings:

```lua theme={null}
-- Preview settings
local max_width = rt.preview.max_width
local image_quality = rt.preview.image_quality

-- Manager settings
local show_hidden = rt.mgr.show_hidden
local show_symlink = rt.mgr.show_symlink
```

### `th` - Theme

Access theme colors and styles:

```lua theme={null}
-- Manager theme
local cwd_style = th.mgr.cwd
local hovered_style = th.mgr.hovered

-- Status bar theme
local status_style = th.status.overall
```

## Using the Lua API

Yazi provides a rich API through the `ya` global:

### Notifications

```lua theme={null}
ya.notify {
  title = "Title",
  content = "Message",
  timeout = 3,  -- Seconds
  level = "info"  -- "info", "warn", "error"
}
```

### User Input

```lua theme={null}
local value, event = ya.input {
  title = "Enter name:",
  pos = { "center", x = 50, y = 50 },
  obscure = false  -- true for password input
}

if event == 1 then
  -- User confirmed
  ya.notify { content = "You entered: " .. value }
end
```

### File Operations

```lua theme={null}
-- Read directory
local entries = fs.read_dir(path, { limit = 100 })

-- Check file attributes
local cha = fs.cha(path)
if cha and cha.is_dir then
  -- It's a directory
end

-- Create/remove
fs.write(path, "content")
fs.remove("file", path)
fs.remove("dir", path)
```

### Running Commands

```lua theme={null}
-- Run command and get output
local output, err = Command("ls")
  :arg({ "-la", "/tmp" })
  :stdout(Command.PIPED)
  :output()

if output then
  ya.notify { content = output.stdout }
end

-- Spawn async process
local child, err = Command("ffmpeg")
  :arg({ "-i", "input.mp4" })
  :stdout(Command.PIPED)
  :stderr(Command.PIPED)
  :spawn()

if child then
  local output = child:wait_with_output()
end
```

### Emitting Events

```lua theme={null}
-- Navigate to directory
ya.emit("cd", { "/path/to/dir", raw = true })

-- Reveal file
ya.emit("reveal", { Url("/path/to/file") })

-- Open file
ya.emit("open", {})

-- Toggle selection
ya.emit("toggle", {})
```

## Plugin Configuration

### Setup Function

Plugins can have a `setup()` function for initialization:

```lua theme={null}
local M = {}

function M:setup(opts)
  opts = opts or {}
  self.auto_save = opts.auto_save or false
  self.interval = opts.interval or 60
  
  if opts.on_init then
    opts.on_init()
  end
end

function M:entry()
  if self.auto_save then
    -- Use configured option
  end
end

return M
```

Call setup in `init.lua`:

```lua theme={null}
require("myplugin"):setup {
  auto_save = true,
  interval = 30
}
```

## Async Programming

For long-running operations, use async:

```lua theme={null}
function M:entry()
  ya.async(function()
    -- This runs in background
    local result = Command("slow-command"):output()
    
    -- Update UI from async
    ya.sync(function()
      ya.notify { content = "Done!" }
    end)
  end)
end
```

## Error Handling

```lua theme={null}
function M:entry()
  local ok, result = pcall(function()
    -- Code that might fail
    return Command("risky-command"):output()
  end)
  
  if not ok then
    ya.notify {
      title = "Error",
      content = tostring(result),
      level = "error"
    }
  end
end
```

## Best Practices

<AccordionGroup>
  <Accordion title="Use local variables">
    Always declare variables with `local` to avoid polluting the global namespace:

    ```lua theme={null}
    -- Good
    local count = 0

    -- Bad
    count = 0  -- Global variable
    ```
  </Accordion>

  <Accordion title="Check for nil values">
    Always validate data before using it:

    ```lua theme={null}
    local hovered = cx.active.current.hovered
    if not hovered then
      return
    end

    -- Safe to use hovered
    local name = hovered.name
    ```
  </Accordion>

  <Accordion title="Use async for slow operations">
    Don't block the UI thread:

    ```lua theme={null}
    -- Good
    ya.async(function()
      local result = Command("slow-cmd"):output()
    end)

    -- Bad - blocks UI
    local result = Command("slow-cmd"):output()
    ```
  </Accordion>

  <Accordion title="Provide user feedback">
    Always notify users of success or failure:

    ```lua theme={null}
    local output, err = Command("tool"):output()
    if not output then
      ya.notify {
        title = "Error",
        content = tostring(err),
        level = "error"
      }
    end
    ```
  </Accordion>
</AccordionGroup>

## Debugging

### Print to stderr

```lua theme={null}
ya.err("Debug value: " .. tostring(value))
```

### Check Yazi logs

Logs are written to:

* Linux/macOS: `~/.local/state/yazi/yazi.log`
* Windows: `%APPDATA%\yazi\state\yazi.log`

## Next Steps

<CardGroup cols={2}>
  <Card title="UI Plugins" icon="palette" href="plugins/ui-plugins">
    Customize Yazi's interface
  </Card>

  <Card title="Functional Plugins" icon="bolt" href="plugins/functional-plugins">
    Add new commands
  </Card>

  <Card title="Previewers" icon="eye" href="plugins/previewers">
    Create file previewers
  </Card>

  <Card title="Fetchers" icon="download" href="plugins/fetchers">
    Build metadata fetchers
  </Card>
</CardGroup>
