---
title: "Use hyperlinks directly in the terminal - Wez's Terminal Emulator"
source: "https://wezterm.org/recipes/hyperlinks.html"
author:
published:
created: 2026-04-17
description: "Wez's Terminal Emulator"
tags:
  - "clippings"
---
## Use hyperlinks directly in the terminal

This recipe is an example on how to leverage hyperlinks directly in the terminal. Hyperlinks are generated by terminal applications by using the [OSC-8 escape sequences](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda).

## Requirements

By default, `ls` doesn't generate hyperlinks, here is an example of aliases to enable them in some common tools:

```sh
alias ls='ls --hyperlink --color=auto'
alias delta="delta --hyperlinks --hyperlinks-file-link-format='file://{path}#{line}'"
alias rg='rg --hyperlink-format=kitty'
```

> On macOS, you'll need to install `ls` from coreutils. For example using [homebrew](https://formulae.brew.sh/formula/coreutils), or use a modern alternative like [eza](https://github.com/eza-community/eza).

## Configuration

With this snippet, you can:

- **Click on an hyperlinked directory** to navigate into that directory and list its contents
- **Click on an hyperlinked file** and if its MIME type is 'text', open it directly in Neovim
- Other hyperlinks like URLs remain unchanged and follow WezTerm's default behavior
```lua
local wezterm = require 'wezterm'
local act = wezterm.action
local config = wezterm.config_builder()

local function is_shell(foreground_process_name)
  local shell_names = { 'bash', 'zsh', 'fish', 'sh', 'ksh', 'dash' }
  local process = string.match(foreground_process_name, '[^/\\]+$')
    or foreground_process_name
  for _, shell in ipairs(shell_names) do
    if process == shell then
      return true
    end
  end
  return false
end

wezterm.on('open-uri', function(window, pane, uri)
  local editor = 'nvim'

  if uri:find '^file:' == 1 and not pane:is_alt_screen_active() then
    -- We're processing an hyperlink and the uri format should be: file://[HOSTNAME]/PATH[#linenr]
    -- Also the pane is not in an alternate screen (an editor, less, etc)
    local url = wezterm.url.parse(uri)
    if is_shell(pane:get_foreground_process_name()) then
      -- A shell has been detected. Wezterm can check the file type directly
      -- figure out what kind of file we're dealing with
      local success, stdout, _ = wezterm.run_child_process {
        'file',
        '--brief',
        '--mime-type',
        url.file_path,
      }
      if success then
        if stdout:find 'directory' then
          pane:send_text(
            wezterm.shell_join_args { 'cd', url.file_path } .. '\r'
          )
          pane:send_text(wezterm.shell_join_args {
            'ls',
            '-a',
            '-p',
            '--group-directories-first',
          } .. '\r')
          return false
        end

        if stdout:find 'text' then
          if url.fragment then
            pane:send_text(wezterm.shell_join_args {
              editor,
              '+' .. url.fragment,
              url.file_path,
            } .. '\r')
          else
            pane:send_text(
              wezterm.shell_join_args { editor, url.file_path } .. '\r'
            )
          end
          return false
        end
      end
    else
      -- No shell detected, we're probably connected with SSH, use fallback command
      local edit_cmd = url.fragment
          and editor .. ' +' .. url.fragment .. ' "$_f"'
        or editor .. ' "$_f"'
      local cmd = '_f="'
        .. url.file_path
        .. '"; { test -d "$_f" && { cd "$_f" ; ls -a -p --hyperlink --group-directories-first; }; } '
        .. '|| { test "$(file --brief --mime-type "$_f" | cut -d/ -f1 || true)" = "text" && '
        .. edit_cmd
        .. '; }; echo'
      pane:send_text(cmd .. '\r')
      return false
    end
  end

  -- without a return value, we allow default actions
end)

return config
```

## Optional configuration

By default, hyperlinks can be opened with a simple mouse click, without any modifier keys. If you find yourself accidentally clicking hyperlinks, you can customize this behavior.

Check the [mouse bindings](https://wezterm.org/config/mouse.html) section for examples on modifying the `OpenLinkAtMouseCursor` action.

For example, to require holding `CTRL` before opening a hyperlink, use the following configuration:

```lua
config.mouse_bindings = {
  {
    event = { Up = { streak = 1, button = 'Left' } },
    mods = 'CTRL',
    action = act.OpenLinkAtMouseCursor,
  },
  -- Disable the 'Down' event of CTRL-Click to avoid weird program behaviors
  {
    event = { Down = { streak = 1, button = 'Left' } },
    mods = 'CTRL',
    action = act.Nop,
  },
}
```

## Drawbacks

This setup sends the text of the commands directly into the active pane which has some drawbacks:

- Does not work inside of any interactive terminal program that is not a shell; the pane must be in a shell prompt
- Requires an empty command prompt before clicking a hyperlink; otherwise, the command may not execute correctly
- When a shell is not detected (see the `is_shell` function), WezTerm falls back to a long shell command, which may clutter the prompt slightly. This happens because WezTerm cannot directly determine a file's MIME type when not connected to a local shell.

If you're using [tmux](https://github.com/tmux/tmux), you'll need to enable the hyperlinks terminal feature and, depending in your configuration (see [bypass mouse reporting modifiers](https://wezterm.org/config/lua/config/bypass_mouse_reporting_modifiers.html)), add `Shift` when clicking an hyperlink.

```js
set -sa terminal-features ",*:hyperlinks"
```