In my work, I routinely need access to fiddly commands, consistent dividers and common things like date and timestamps. This is where a program like Espanso really shines. While not as powerful as TextExpander for Mac, Espanso is a good Linux and Windows solution that can still do quite a lot. The idea behind these programs is that they run in the background listening for you to type a macro trigger at which point they replace the trigger with whatever you have defined in your match rules.

For example, I can type something like dddate and it will expand to the current date 07/26/2025. Cool. One problem I have found is that it is hard to find good macro examples online. The best macros come from experience–when you identify things that are difficult to remember or do consistently, automate them with a new rule. However, it helps to have a few to get you started and show what is possible.

The Short Version: What’s in This Post?

  • A real set of Espanso macros I actually use
  • What each one does and when it’s a lifesaver
  • Notes on setup and quirks, especially on Windows/WSL
  • A few ways to invent your own macros.

Setting up Espanso

Install Espanso (see official docs), then put your macros in the config file—usually:

  • Linux/macOS: ~/.config/espanso/match/default.yml
  • Windows: %APPDATA%\espanso\match\default.yml

After edits, run espanso restart to reload or reload from the system tray icon on Windows.

👉 Heads up YAML is picky—use 2 spaces for indentation, never tabs, and watch your quotes.

My macros

Minus a few that I am leaving out like my home address, these are my current Espanso macros. If you are on Mac, TextExpander makes this all easy with a nice GUI and cloud macros that you can use by installing the app and logging in. Espanso is simpler and requires that you add your macros in YAML format to default.yml

On my system I have a few handy macros defined.


matches:

  # WAN/Public IP - Uses WSL curl, shows your external IP
  - trigger: ":wanip"
    replace: "{{wanip}}"
    vars:
      - name: wanip
        type: shell
        params:
          cmd: "wsl curl -s ifconfig.me"

  # LAN/Internal IP - Uses WSL and grabs your main IP
  - trigger: ":lanip"
    replace: "{{lanip}}"
    vars:
      - name: lanip
        type: shell
        params:
          cmd: 'wsl bash -c "ip addr show eth0 | grep \"inet \" | awk \"{print \$2}\" | cut -d/ -f1 | xargs"'

  - trigger: ":forglob"
    replace: |
      for file in {{glob}}; do
        [[ -f "$file" ]] || continue
        # do something with "$file"
        echo "$file"
      done


  ### ─────────────────────────────
  ### HORIZONTAL SEPARATORS
  ### ─────────────────────────────

  - trigger: ":#--"
    replace: "# ────────────────────────────────────────────────────────────────"

  - trigger: ":---"
    replace: "---------------------------------------------"

  - trigger: ":=="
    replace: "========================================"

  - trigger: ":fence"
    replace: |
      # ─────────────────────────────────────────────────────────────
      #
      # ─────────────────────────────────────────────────────────────

  - trigger: ":hashfence"
    replace: |
      ##############################################################
      #
      ##############################################################

  - trigger: ":hdr"
    label: "Insert Bash-style header with cursor at name"
    replace: |
      # ─────────────────────────────────────────────
      # [[CURSOR]]
      # ─────────────────────────────────────────────

  ### ─────────────────────────────
  ### AUTHOR LINE / BOILERPLATE
  ### ─────────────────────────────

  - trigger: ":author"
    replace: "# Author: forfaxx {{date:YYYY}}"

  ### ─────────────────────────────
  ### DATE & TIME MACROS
  ### ─────────────────────────────

  - trigger: "dddate"
    replace: "{{mydate}}"
    vars:
      - name: mydate
        type: date
        params:
          format: "%m-%d-%Y"

  - trigger: "tttime"
    replace: "{{mytime}}"
    vars:
      - name: mytime
        type: date
        params:
          format: "%H:%M:%S"

  - trigger: ":now"
    replace: "{{timestamp}}"
    vars:
      - name: timestamp
        type: date
        params:
          format: "%Y-%m-%d %H:%M:%S"

  - trigger: "bkpfile"
    replace: "backup-{{bkptime}}.tar.gz"
    vars:
      - name: bkptime
        type: date
        params:
          format: "%Y-%m-%d-%H-%M-%S"

  - trigger: ":fortune"
    replace: "{{fortune}}"
    vars:
      - name: fortune
        type: shell
        params:
          cmd: 'wsl fortune'


  ### ----------------------------
  ###  BOILERPLATE
  ### ----------------------------

  - trigger: ":lorem"
    replace: |
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.


  - trigger: ":mit"
    replace: |
      # MIT License
      #
      # Copyright (c) Kevin Joiner {{mydate}}
      #
      # Permission is hereby granted, free of charge, to any person obtaining a copy
      # of this software and associated documentation files (the "Software"), to deal
      # in the Software without restriction, including without limitation the rights
      # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      # copies of the Software, and to permit persons to whom the Software is
      # furnished to do so, subject to the following conditions:
      #
      # The above copyright notice and this permission notice shall be included in all
      # copies or substantial portions of the Software.
      #
      # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      # SOFTWARE.
    vars:
      - name: mydate
        type: date
        params:
          format: "%Y"

Usage Tips and Troubleshooting

After reloading, you should be able to trigger any of the macros by typing the trigger (the string inside the double quotes) and it will expand. If you have any trouble with a macro, check for YAML errors or conflicting triggers.

The Espanso logs are located at:

  • Linux/macOS: ~/.config/espanso/logs/espanso.log
  • Windows: %APPDATA%\espanso\logs\espanso.log

%APPDATA% typically resolves to something memorable like C:\Users\<your username>\AppData\Roaming\espanso\Logs\espanso.log

If all else fails, try restarting Espanso completely or testing in a different editor, like Visual Studio Code or Notepad++.

Pain Points

As stated previously, YAML is very picky about indentation and will happily refuse to work if you are even one space or an errant tab off. A good editor like the amazing VSCode is invaluable here since it makes spacing and alignment easy to manage and will report if you have a syntax error.

One compelling feature in Espanso is the ability to position the cursor to a specific place in the macro with [[CURSOR]]. In my experience this is difficult to get working on Windows but works reliably on Linux. Your mileage may vary.

The other potentially powerful feature that is difficult on Windows is calling external programs from your macro. This is tantalizing if you run Linux under Windows Subsystem for Linux under Windows since you can run Linux shell commands by using wsl.exe like so:

  # WAN/Public IP - Uses WSL curl, shows your external IP
  - trigger: ":wanip"
    replace: "{{wanip}}"
    vars:
      - name: wanip
        type: shell
        params:
          cmd: "wsl curl -s ifconfig.me"

This works great on my setup. Much more difficult to execute are commands that use pipes for things like grep or awk with complex quoting. You can make it work but heed my warning, that way lies MADNESS.

Aside from these and a few other limitations, Espanso is a very useful tool to have in your collection. While it doesn’t (yet) support dropdowns, fill-ins or advanced logic like TextExpander it is lightweight and works quite well despite those limitations.

Conclusion

There you have it. My humble espanso macros that along with my shell aliases, make it easy to remember and be consistent in my work. Using my hard-won experience, you should be able to create your own macros for things like boilerplate you are sick of typing, nicely worded explanations for emails, commands and even scripts.

Have an awesome macro or idea for one? I’d love to hear about it! feedback@adminjitsu.com

May your macros always expand!