Estimated reading time at 200 wpm: 12 minutes
Espanso is a free, open-source text expander that works across Windows, macOS and Linux. Most guides cover the basics — type a trigger, get some text. This guide goes further, covering features that turn Espanso from a simple text replacer into a genuinely powerful productivity tool. For those to want to save the drudgery of repeatedly typing the same batch of text over and over, it’s a game changer. If you are a healthworker, lawyer, software-programmer, writer or just and ordinary person who uses snippets of text, the investment of time here saves time in the collective future.
Whether or not you agree our Fat Disclaimer applies
Look: “Use only UK spelling and grammar, except where originating from another source.” I didn’t type that. I just typed /uksg and out it came in less that 0.1 second. Then “Let me know what you think.” – I typed /lmk. Or “But in a free and democratic society everybody has choice!” – I typed /fds. That’s basic stuff which will suffice for most users who want some time-and-effort efficiency to build up over say a year. This post is more about advanced use cases e.g. forms, drop-down menus, outputting images, and using PowerShell. I’ll cover some tweaks for Linux.
It will be Espanso is already installed and working with basic triggers in Windows or Linux. Guide means guide – this is not a hand-holding tutorial or instruction manual. That means you can’t just expect to do a cut and paste and modification of code, and ‘Bingo – you’re in business’. If you’re one of those types you should leave now and save time for your Instagram surfing.
Understanding Forms
In some text expanders, placing a placeholder like [[name]] inside a snippet automatically creates a pop-up input box. Espanso does not work this way. Forms must be explicitly defined in the vars section of a snippet.
The Core Pattern
Every form-based snippet follows this structure:
matches:
- trigger: "/greet"
replace: "Dear {{form1.name}}, thank you for your message on {{form1.date}}."
vars:
- name: form1
type: form
params:
layout: |
Recipient Name: [[name]] Date of Message: [[date]]
When /greet is typed, a pop-up form appears with two text boxes. After clicking Submit (or pressing Enter), the values are inserted into the replace template.
How the Naming Works
The connection between the form and the output template follows dot notation:
form1is the name assigned to the form tool undervars. This could be any name —myform,inputbox,fred— it just has to be consistent.[[name]]inside thelayoutcreates a field calledname.{{form1.name}}in thereplaceblock fetches the value from thenamefield inside the tool calledform1.
The left side of the dot refers to which tool. The right side refers to which field inside that tool. The names simply have to match.
Dropdown Menus in Forms
Forms are not limited to free-text boxes. Dropdown lists can be created using the values property:
matches:
- trigger: "/assess"
replace: |
Risk Level: {{form1.risk}}
Setting: {{form1.setting}}
Outcome: {{form1.outcome}}
vars:
- name: form1
type: form
params:
layout: |
Risk Level: [[risk]] Setting: [[setting]] Outcome: [[outcome]] values:
risk:
type: choice
values:
- Low
- Medium
- High
setting:
type: choice
values:
- Outpatient
- Community
- Inpatient
outcome:
type: choice
values:
- Discharge
- Follow-up in 1 week
- Follow-up in 4 weeks
- Urgent referral
This produces a form with three dropdown menus instead of free-text fields. Useful for any situation where responses are standardised.
Forms Without Shell Commands
An important point: not every form needs a shell command. If the task is simply assembling text from user input, the replace block handles it directly. No PowerShell, no shell, no computation.
For example, a template letter:
matches:
- trigger: "/refletter"
replace: |
Dear {{form1.recipient}}
Re: {{form1.patient_name}} (DOB: {{form1.dob}})
Thank you for referring the above-named patient. An appointment has been
arranged for {{form1.appt_date}} at {{form1.location}}.
Kind regards
vars:
- name: form1
type: form
params:
layout: |
Recipient: [[recipient]] Patient Name: [[patient_name]] Date of Birth: [[dob]] Appointment Date: [[appt_date]] Location: [[location]]
The rule of thumb: forms collect, replace assembles, and shell computes. Only bring in the shell when the other two cannot do the job alone.
Using PowerShell for Calculations
When a snippet needs to calculate, transform or process data, a shell command is required. On Windows, the cleanest approach is to tell Espanso to use PowerShell directly.
The Key: shell: powershell
Without specifying the shell, Espanso uses the default Windows command processor. Embedding PowerShell commands inside it requires layers of escaped quotes and quickly becomes unreadable. The shell: powershell parameter avoids this entirely.
Example: Calculating a Duration
matches:
- trigger: "/duration"
replace: "Duration: {{result}}"
vars:
- name: form1
type: form
params:
layout: |
Start Time (HH:MM): [[start_time]] End Time (HH:MM): [[end_time]] - name: result
type: shell
params:
shell: powershell
cmd: |
$s=[datetime]::Parse('{{form1.start_time}}')
$e=[datetime]::Parse('{{form1.end_time}}')
$m=($e-$s).TotalMinutes
'{0}h {1}m' -f [math]::Floor($m/60), ($m%60)
Typing /duration pops up a form asking for start and end times. After submission, PowerShell calculates the difference and outputs something like Duration: 1h 39m.
YAML Block Scalars
The | character after cmd: is a YAML block scalar. It allows multi-line commands to be written naturally, without wrapping everything in a single quoted string. This is especially valuable for shell commands where quoting is already complex.
Organising Snippets Across Multiple Files
As the number of snippets grows, a single match.yml file becomes unwieldy. Espanso solves this simply: it automatically loads every .yml file inside the match folder.
Folder Structure
espanso/
config/
default.yml
match/
global.yml
clinical.yml
timesheets.yml
personal.yml
tools.yml
medications.yml
ai_prompts.yml
Each file needs the standard matches: header:
matches:
- trigger: "/hello"
replace: "Hello, world."
No configuration is needed to link the files. Espanso scans the entire match folder and loads every .yml file it finds.
The only rule: avoid duplicate triggers across files. Espanso will not stop it, but behaviour becomes unpredictable.
Global Variables
If multiple snippets across different files share a common variable (such as a date format), it can be defined once using global_vars in its own file:
global_vars:
- name: current_date
type: date
params:
format: "%Y-%m-%d %H:%M"
matches: []
The matches: [] line is necessary because Espanso expects that key to be present even if the file only defines global variables. Once defined, {{current_date}} is available in every snippet across every file.
Pasting Images
Espanso can paste images using the image_path property. Unlike HTML-embedded approaches (such as base64-encoded images), Espanso pastes the image via the clipboard. This means it works in any application that accepts pasted images — including messaging apps like WhatsApp, Telegram, Signal and Slack, where HTML-based methods typically fail.
Basic Syntax
matches:
- trigger: "/logo"
image_path: "D:/CloudSync/espanso-images/company-logo.png"
Organising Image Snippets
A dedicated file keeps things tidy:
# images.yml
matches:
#SIGNATURES
- trigger: "/sigimg"
image_path: "D:/CloudSync/espanso-images/signature.png"
#REACTIONS
- trigger: "/shitstorm"
image_path: "D:/CloudSync/espanso-images/reactions/shitstorm.png"
- trigger: "/facepalm"
image_path: "D:/CloudSync/espanso-images/reactions/facepalm.png"
#EXCLAMATIONS
- trigger: "/wow"
image_path: "D:/CloudSync/espanso-images/exclamations/wow.png"
Cross-Machine Setup with Cloud Storage
Since image_path requires a local file path, the images must exist on each machine. A cloud-synced folder (OneDrive, Google Drive, Dropbox) handles this automatically — the images sync locally and Espanso reads from the local copy.
If the cloud sync path is identical across machines (e.g. both Windows desktops map to D:/OneDrive/), a single images file works everywhere.
OneDrive on Linux
Unlike Windows, where OneDrive integrates natively with the file system, there is no official OneDrive client for Linux. Microsoft does not provide one. This means that cloud-synced image folders are not automatically available on a Linux machine.
The most reliable solution is Rclone, a command-line tool that can mount cloud storage providers — including OneDrive — as local directories. Once configured, Rclone mounts OneDrive to a local path (e.g. /mnt/onedrive/), making the files accessible to Espanso as though they were stored locally.
Setting up Rclone with OneDrive requires some initial configuration (authenticating with a Microsoft account, creating a remote, and setting up a mount point), but once working it runs reliably in the background. There are various guides available online, and it is worth the effort for anyone running a dual-boot setup who relies on OneDrive for shared files. For more on Rclone setting up on Linux go: Rethinking Cloud Mounts on Linux: From Volatility to Rclone
Other tools exist (such as onedrive for Linux or Insync). However there are bottlenecks and pitfalls that were explained before. Rclone is free, well-maintained, and supports a wide range of cloud providers beyond just OneDrive.
If paths differ between operating systems, create separate files:
images_w11.yml— uses Windows paths (e.g.D:/OneDrive/...)images_lnx.yml— uses Linux mount paths (e.g./mnt/onedrive/...)
Duplicate the content, then find-and-replace the base path. Only place the correct version in each machine’s match folder. Having both loaded simultaneously would create duplicate triggers pointing to non-existent paths.
Windows File Path Note
When copying file paths from Windows Explorer, backslashes are used (e.g. D:\OneDrive\Images\logo.png). In YAML, single backslashes act as escape characters. Two options:
- Use forward slashes (recommended):
D:/OneDrive/Images/logo.png— Windows accepts these. - Use double backslashes:
D:\\OneDrive\\Images\\logo.png
A quick Ctrl+H find-and-replace of \ with / in any text editor handles bulk conversion. VS Code is highly recommended as an editor.
Base64-Embedded Images (Alternative Approach)
It is also possible to embed images directly into the YAML as base64-encoded HTML:
matches:
- trigger: "/logo"
html: "<img src='data:image/png;base64,iVBORw0KGgo.....' />"
This eliminates the need for external files and works across any machine without path concerns. However, it has two drawbacks: the YAML file becomes extremely large (a single image can add hundreds of kilobytes), and it only works in applications that accept rich text (email clients, word processors — not messaging apps).
For frequently used images, the file-based approach is generally preferable. If you want to understand base64-enconding, you can spend 10 years on their website. In a free and democratic society everybody has choice! I don’t need to understand it to use the code in certain specific circumstances.
Launching Documents from a Trigger
Espanso can open files and applications using shell commands. The trigger fires, the file opens, and nothing is typed into the current document.
Windows
matches:
- trigger: "/opentemplate"
replace: ""
vars:
- name: launch
type: shell
params:
shell: powershell
cmd: |
Start-Process "D:/OneDrive/Templates/ReviewTemplate.docx"
Start-Process opens the file in its default application. A .docx opens in Word, a .pdf in the default PDF reader, a .xlsx in Excel, and so on. The replace: "" ensures nothing is inserted into the current document.
Linux
matches:
- trigger: "/opentemplate"
replace: ""
vars:
- name: launch
type: shell
params:
cmd: |
xdg-open "/home/user/OneDrive/Templates/ReviewTemplate.docx"
xdg-open is the Linux equivalent, opening files with the system’s default application.
A Note on Scope
Launching documents stretches Espanso beyond its core purpose as a text expander. It works well for occasional use. For heavy use of application and document launchers, a dedicated automation tool (such as AutoHotkey on Windows) may be more appropriate.
Quick Reference: When to Use What
| Need | Tool | Shell needed? |
|---|---|---|
| Insert fixed text | replace | No |
| Insert text with user input | replace + form | No |
| Calculate or transform data | replace + form + shell | Yes |
| Paste an image | image_path | No |
| Open a file or application | shell only | Yes |
Summary
The three building blocks — forms to collect, replace to assemble, and shell to compute — cover the vast majority of what Espanso can do. Understanding how they connect (especially the dot notation between form names and field names) makes building new snippets straightforward. Adding image support and file launching extends Espanso well beyond simple text replacement, particularly for users migrating from tools like Text Blaze that handle some of these features differently.











