Skip to content

Espanso: a better way to type

Changing your way of typing with macros and automations.

Have you heard of espanso? I have been using it to automate small tasks while typing, such as inserting a date or generating an ID.

Installation

Start by installing espanso from its download page. Select your operating system, then open a terminal and run:

espanso status

A working installation prints espanso is running. The first time you open it, a short tutorial asks you to type :espanso; espanso replaces it with Hi there.

That small substitution is the basic idea. The examples below show how far it can go.

Configuration

Before writing a configuration, find espanso's configuration directory:

espanso path

The command prints three paths: Config, Packages, and Runtime. We need the path shown for Config. Open it in your editor β€” neovim in my case:

cd "$(espanso path config)"
code # Using vscode to be more familiar

Espanso directoryEspanso directory

The directory contains several .yml files. base.yml is the main configuration file and contains the substitution rules.

Choose a consistent prefix for espanso triggers. Configurations and packages often use :, but ; avoids conflicts with Slack, Notion, and similar applications. It also means you do not need Shift + ; to type a colon.

Substitutions

Now that we know which file to edit, let us look at espanso's substitution types.

Add the following blocks to the matches array in base.yml.

Static substitution

Static substitution is the simplest type. Any message or value that you repeatedly copy and paste can become an espanso macro.

- trigger: ;email
  replace: [email protected]
- trigger: ;ssn
  replace: 123-45-6789 # Fake SSN generated by my espanso :D

Static substitutions work well when you only need to insert text at the cursor. They can fill common form fields such as email, ID, phone number, or full name.

I also use them for social links, including my YouTube channel, LinkedIn, Twitter/X, and for inserting emojis.

Dynamic substitution

Espanso also provides directives for dynamic values, such as the current time:

- trigger: ;now
  replace: {{time}}
  vars:
    - name: time
	type: date
	params:
	  format: "%H:%M"

This uses chrono tokens, a Rust library for date formatting. You can also use parameters such as offset to create triggers for yesterday or tomorrow.

The random extension chooses one value from a list. I use it for ZIP codes: instead of generating an invalid number, I keep a list of known valid codes and select one for autocomplete.

  - label: "Random ZIP"
    replace: '{{zip}}'
    trigger: ;zip
    vars:
      - name: zip
        type: random
        params:
          choices:
            - 10001
            - 90210
            - 33101
            - 60601
            - 98101
            - 02101
            - 85001
            - 30301

Now ;zip inserts a valid ZIP code selected from the list.

These examples use espanso's match extensions, variables with the {{VARIABLE_NAME}} syntax, and trigger labels through the - label: property.

Integrating your scripts

Espanso can also execute shell commands when a trigger is typed. This lets you generate values dynamically with any command available on your system, not only bash, zsh, or fish. For example, you can write a program in Deno and use its output as the replacement.

Start with the example from the official documentation:

  - trigger: ":ip"
    replace: "{{output}}"
    vars:
      - name: output
        type: shell
        params:
          cmd: "curl 'https://api.ipify.org'"

This uses curl to make a GET request to ipify.org and return your external IP.

Command execution can vary between operating systems, so check the command syntax for your platform. With that limitation in mind, let us explore a few uses.

ID Generator

Developers often use online tools to generate test IDs and documents. The workflow is usually:

  • Open the browser
  • Enter the URL
  • Find the right option
  • Click to generate

With espanso, typing ;uuid can insert a valid UUID at the cursor without leaving the editor or terminal.

As mentioned above, espanso can execute programs written in any language. For this example, use a small UUID generator:

  - trigger: ";uuid"
    replace: "{{uuid}}"
    vars:
      - name: uuid
        type: shell
        params:
          cmd: "node -e 'console.log(require(\"node:crypto\").randomUUID())'"

From here, you can turn many repetitive tasks into an espanso flow. Colleagues who adopted it now use it for email templates, automatic form filling, and ID generation.

Conclusion

I have used espanso for more than a year and a half, and it has changed how I work with forms, email, data entry, SQL queries, code snippets, and more. The complete set of automations is in my espanso configuration file.

The main drawback for me is writing YAML. My configuration is therefore written in TypeScript and Deno, which avoids spacing and indentation mistakes. If you prefer YAML, the equivalent configuration is available in this gist.

Thanks for reading.