Ulauncher 5.0 (Extension API v2.0.0)¶
Here you can find only docs on extensions and color themes. Everything else is on Github Wiki.
Note
To find out how to migrate your extension from API v1 to v2 navigate to Extension Migration
Custom Color Themes¶
Color Themes¶
Ulauncher comes with built-in color themes you can choose between. In addition to that you can install community contributed themes or create you own.
Installing Community Themes¶
If you find a community contributed theme you like, this is how you install it:
- Ensure that you have the user theme directory
mkdir -p ~/.config/ulauncher/user-themes
- Move to the user theme directory
cd ~/.config/ulauncher/user-themes
- Clone the theme
git clone git@github.com:<user_name>/<theme_name>.git
(replace with the actual user name and theme name) - Open Ulauncher Preferences and select the theme
Creating Custom Color Themes¶
You can only change colors in themes. Changing element sizes is not possible due to restrictions in the GTK+ API.
- Take a look at how the built-in themes are written
- Ensure that you have the user theme directory
mkdir -p ~/.config/ulauncher/user-themes
- Copy an existing theme directory to this directory.
- Rename the copied directory and change the name and display_name in
manifest.json
- Open Ulauncher Preferences and select your theme
- Edit colors in CSS files
- Tell Ulauncher to re-read theme files by running
kill -HUP <PID>
- Press
Ctrl+Space
(or your hotkey) to check the result - Repeat 6 - 8 until you get a desired result
You might find these two wiki entries on GTK+ CSS useful:
manifest.json¶
Use the following template:
{
"manifest_version": "1",
"name": "adwaita",
"display_name": "Adwaita",
"extend_theme": "light",
"css_file": "theme.css",
"css_file_gtk_3.20+": "theme-gtk-3.20.css",
"matched_text_hl_colors": {
"when_selected": "#99ccff",
"when_not_selected": "#99ccff"
}
}
manifest_version
- version ofmanifest.json
file. Current version is “1”name
- used to uniquely identify themedisplay_name
- is displayed in a list of theme options in preferencesextend_theme
- can benull
or a name of an existing theme you’d like to extendcss_file
- name of your css filecss_file_gtk_3.20+
- name css file for GTK+ v3.20 or highermatched_text_hl_colors
- Colors of characters in name or description of an item that match with your query. Must containwhen_selected
andwhen_not_selected
colors.
Note
All fields except extend_theme
are required and cannot be empty.
- Color Themes
- Create your own color themes
Extension Development Guide¶
Overview¶
What is an Extension¶
Ulauncher extensions are Python 3 programs that run as separate processes along with the app.
When you run Ulauncher it starts all available extensions so they are ready to react to user events. All extensions are terminated when Ulauncher app is closed or crashed.
What Extensions Can Do¶
Extensions have the same capabilities as any other program – they can access your directories, make network requests, etc. Basically they get the same rights as a user that runs Ulauncher.
Extension API v2 (current) enables extension developers to write custom handlers for keywords.

“ti” is a keyword, the rest of the query is an argument in this case.
With Extension API it is possible to capture event when user enters “ti<Space>” into the input and then render any results below the input box.
Extensions can define preferences in manifest.json
that can be overridden by a user
from Ulauncher Preferences window.
It is also possible to capture item click (enter) event and run a custom function to respond to that event.
What Extensions Cannot Do¶
They cannot modify behaviour or look of Ulauncher app. They can only be used to handle an input that starts with a keyword, which extension developers define in a manifest file.
Ulauncher ⇄ Extension Communication Layer¶
Ulauncher communicates to extensions using WebSockets.
For developer convenience there is an abstraction layer over WebSockets that reduces amount of boilerplate code in extensions.

Message flow
Development Tutorial¶
Creating a Project¶
Ulauncher runs all extensions from ~/.local/share/ulauncher/extensions/
.
Create a new directory there (name it as you wish) with the following structure:
.
├── images
│ └── icon.png
├── versions.json
├── manifest.json
└── main.py
versions.json
contains mapping of Ulauncher Extension API to branch name of the extensionmanifest.json
contains all necessary metadatamain.py
is an entry point for your extensionimages/
contains at least an icon of you extension
Check out Debugging & Logging to learn how to test and debug your extension.
versions.json¶
The file contains a list with supported versions of Ulauncher API. commit
field may be either a commit id, branch name, or git tag where the code for that required version is located
versions.json
must be checked in to the root dir of master branch.
required_api_version
must contain a specific version or a range of versions defined using NPM Semver format.
Let’s take this example:
[
{"required_api_version": "^1.0.0", "commit": "release-for-api-v1"},
{"required_api_version": "^2.0.0", "commit": "release-for-api-v2"},
{"required_api_version": "^2.3.1", "commit": "master"}
]
release-for-api-v1
is a branch name (or may be a git tag in this case too). You can choose branch/tag names whatever you like.
^1.0.0
means that the Ulauncher will install extension from branch release-for-api-v1
if Ulauncher Extension API >= 1.0.0 and < 2.0.0
If for example the current API version is 2.5.0
, which matches both ^2.0.0
and ^2.3.1
requirements, then Ulauncher will install extension from master
branch because it chooses the last matched item.
You can find the current version on the About page of Ulauncher preferences.
What problem does versions.json solve?
We want to minimize the amount of code and infrastructure components that are needed to have a flexible extension ecosystem. For that reason we want to rely on Github as much as possible as a storage of extensions. We also want to allow extension developers to release extensions for previous versions of Ulauncher (for bug fixes for example). That’s why versions.json
will be used to track all releases of a certain extension.
How does Ulauncher use this file?
versions.json
from the master branch of the extension repo.manifest.json¶
Create manifest.json
using the following template:
{
"required_api_version": "^2.0.0",
"name": "Demo extension",
"description": "Extension Description",
"developer_name": "John Doe",
"icon": "images/icon.png",
"options": {
"query_debounce": 0.1
},
"preferences": [
{
"id": "demo_kw",
"type": "keyword",
"name": "Demo",
"description": "Demo extension",
"default_value": "dm"
}
]
}
required_api_version
- a version of Ulauncher Extension API that the extension requires. It should follow NPM Semver format. In most of the cases you would want to specify a string like^x.y.z
wherex.y.z
is the current version of extension API (not Ulauncher). You can find the current version number on the About page of Ulauncher preferences.name
,description
,developer_name
can be anything you like but not an empty stringicon
- relative path to an extension iconoptions
- dictionary of optional parameters. See available options belowpreferences
- list of preferences available for users to override. They are rendered in Ulauncher preferences in the same order they are listed in manifest.
Note
All fields except options
are required and cannot be empty.
Available Options¶
query_debounce
Default
0.05
. Delay in seconds between event is created and sent to your extension.If a new event is created during that period, previous one is skipped. Debounce helps to prevent redundant events caused by user typing too fast or maybe some other reasons when you may not want to process events each time they are triggered.
If your extension is super responsive (i.e, doesn’t wait for I/O operations like network requests, file read/writes, and doesn’t load CPU, you may want to set a lower value like
0.05
or0.1
. Otherwise it’s recommended to set value to1
or higher.
Preference Object Fields¶
The values of the preferences are forwarded to the on_event
method of the KeywordQueryEventListener
class as an attribute of extension. For example the value of the keyword with id = 'id'
and value = 'val'
is obtained with the line value = extension.preferences['id']
which assigns the string 'val'
to value. An example of the use of preferences can be found in the ulauncher demo extension
id
(required)- Key that is used to retrieve value for a certain preference
type
(required)Can be “keyword”, “input”, “text”, or “select”
- keyword - define keyword that user has to type in in order to use your extension
- input - rendered as
<input>
- text - rendered as
<textarea>
- select - rendered as
<select>
with a list of options
Note
At least one preference with type “keyword” must be defined.
name
(required)- Name of your preference. If type is “keyword” name will show up as a name of item in a list of results
default_value
- Default value
description
- Optional description
options
- Required for type “select”. Must be a list of strings or objects like:
{"value": "...", "text": "..."}
Note
All fields except description
are required and cannot be empty.
main.py¶
Copy the following code to main.py
:
from ulauncher.api.client.Extension import Extension
from ulauncher.api.client.EventListener import EventListener
from ulauncher.api.shared.event import KeywordQueryEvent, ItemEnterEvent
from ulauncher.api.shared.item.ExtensionResultItem import ExtensionResultItem
from ulauncher.api.shared.action.RenderResultListAction import RenderResultListAction
from ulauncher.api.shared.action.HideWindowAction import HideWindowAction
class DemoExtension(Extension):
def __init__(self):
super().__init__()
self.subscribe(KeywordQueryEvent, KeywordQueryEventListener())
class KeywordQueryEventListener(EventListener):
def on_event(self, event, extension):
items = []
for i in range(5):
items.append(ExtensionResultItem(icon='images/icon.png',
name='Item %s' % i,
description='Item description %s' % i,
on_enter=HideWindowAction()))
return RenderResultListAction(items)
if __name__ == '__main__':
DemoExtension().run()
Now restart Ulauncher.
Tip
Run ulauncher -v
from command line to see verbose output.

When you type in “dm ” (keyword that you defined) you’ll get a list of items. This is all your extension can do now – show a list of 5 items.
Basic API Concepts¶

Message flow
1. Define extension class and subscribe to an event
Create a subclass of
Extension
and subscribe to events in__init__()
.class DemoExtension(Extension): def __init__(self): super().__init__() self.subscribe(KeywordQueryEvent, KeywordQueryEventListener())
self.subscribe(event_class, event_listener)
In our case we subscribed to one event –
KeywordQueryEvent
. This means whenever user types in a query that starts with a keyword from manifest file,KeywordQueryEventListener.on_event()
will be invoked.
2. Define a new event listener
Create a subclass of
EventListener
and implementon_event()
class KeywordQueryEventListener(EventListener): def on_event(self, event, extension): # in this case `event` will be an instance of KeywordQueryEvent ...
on_event()
may return an action (see Actions).
3. Render results
Return
RenderResultListAction
in order to render results.ExtensionResultItem
describes a single result item.You can also use
ExtensionSmallResultItem
if you want to render more items. You won’t have item description with this type.class KeywordQueryEventListener(EventListener): def on_event(self, event, extension): items = [] for i in range(5): items.append(ExtensionResultItem(icon='images/icon.png', name='Item %s' % i, description='Item description %s' % i, on_enter=HideWindowAction())) return RenderResultListAction(items)
on_enter
is an action that will be ran when item is entered/clicked.
4. Run extension
if __name__ == '__main__': DemoExtension().run()
Custom Action on Item Enter¶
1. Pass custom data with ExtensionCustomAction
Instantiate
ExtensionResultItem
withon_enter
that is instance ofExtensionCustomAction
data = {'new_name': 'Item %s was clicked' % i} ExtensionResultItem(icon='images/icon.png', name='Item %s' % i, description='Item description %s' % i, on_enter=ExtensionCustomAction(data, keep_app_open=True))
data
is any custom data that you want to pass to your callback function.Note
It can be of any type as long as it’s serializable with
pickle.dumps()
2. Define a new listener
from ulauncher.api.client.EventListener import EventListener class ItemEnterEventListener(EventListener): def on_event(self, event, extension): # event is instance of ItemEnterEvent data = event.get_data() # do additional actions here... # you may want to return another list of results return RenderResultListAction([ExtensionResultItem(icon='images/icon.png', name=data['new_name'], on_enter=HideWindowAction())])
3. Subscribe to ItemEnterEvent
You want your new listener to be subscribed to
ItemEnterEvent
like this:from ulauncher.api.shared.event import KeywordQueryEvent, ItemEnterEvent class DemoExtension(Extension): def __init__(self): super().__init__() self.subscribe(KeywordQueryEvent, KeywordQueryEventListener()) self.subscribe(ItemEnterEvent, ItemEnterEventListener()) # <-- add this line

Now this will be rendered when you click on any item
Note
Please take a short survey to help us build greater API and documentation
Events¶
KeywordQueryEvent¶
ItemEnterEvent¶
Is triggered when selected item has action of type
ExtensionCustomAction
Whatever data you’ve passed to action will be available in in this class using methodget_data()
Parameters: data (str) – Returns: whatever object you have passed to ExtensionCustomAction
SystemExitEvent¶
Is triggered when extension is about to be terminated.
Your extension has 300ms to handle this event and shut down properly. After that it will be terminated with SIGKILL
PreferencesUpdateEvent¶
Is triggered when user updates preference through Preferences window
Parameters: - id (str) –
- old_value (str) –
- new_value (str) –
PreferencesEvent¶
Is triggered on start
Parameters: preferences (dict) –
Actions¶
ExtensionCustomAction¶
If initiated with
data
, the same data will be returned inItemEnterEvent
objectParameters: - data (any) – any type that can be serialized with
pickle.dumps()
- keep_app_open (bool) – pass
True
if you want to keep Ulauncher window open.False
by default
- data (any) – any type that can be serialized with
ActionList¶
Used to run multiple action at once
Parameters: actions (list) – list of actions to run
CopyToClipboardAction¶
DoNothingAction¶
Does nothing. Keeps Ulauncher window open
HideWindowAction¶
Does what the class name says
LaunchAppAction¶
OpenAction¶
Run platform specific command to open either file or directory
Parameters: path (str) – file or dir path
OpenUrlAction¶
Opens URL in a default browser
Parameters: url (str) –
RenderResultListAction¶
RunScriptAction¶
Runs a user script
Parameters: - script (str) – script content
- args (list) – arguments
SetUserQueryAction¶
Note
Please take a short survey to help us build greater API and documentation
Available Libraries¶
Currently it’s not possible to define python packages required for your extension. However, libraries listed below are available to use in extensions since they are required for Ulauncher and are pre-installed with the app.
In future we’ll make it possible to support requirements.txt
for extensions.
- gir1.2-gtk-3.0
- GTK+ 3.0
- gir1.2-keybinder-3.0
- Library for registering global key bindings for gtk-based applications in X11.
- gir1.2-webkit2-4.0
- JavaScript engine library from WebKitGTK+
- gir1.2-glib-2.0
- Low level core library
- gir1.2-notify-0.7
- Desktop notification library (libnotify is a library for sending desktop notifications)
- gir1.2-gdkpixbuf-2.0
- An image loading library
- gir1.2-appindicator3-0.1
- Allow applications to export a menu into the panel
- python-dbus
- Python DBus library.
- python-pyinotify
- Monitoring filesystems events with inotify on Linux
- python-pysqlite2
- DB-API 2.0 interface for SQLite databases
- python-websocket
- websocket client for python
- python-xdg
- Python library supporting various freedesktop standards.
Debugging & Logging¶
Run Extension Separately¶
You don’t have to restart Ulauncher every time a change is made to your extension.
For your convenience there is a flag --no-extension
that prevents extensions from starting automatically.
First, start Ulauncher with the following command:
ulauncher --no-extensions --dev -v
Then find in the logs command to run your extension. It should look like this:
VERBOSE=1 ULAUNCHER_WS_API=ws://127.0.0.1:5050/ulauncher-demo PYTHONPATH=/home/username/projects/ulauncher /usr/bin/python /home/username/.local/share/ulauncher/extensions/ulauncher-demo/main.py
Now when you need to restart your extension hit Ctrl+C
and run the last command again.
Debugging With ipdb¶
Here is the easiest way to set a breakpoint and execute code line by line:
- Install ipdb
sudo pip install ipdb
- In your code add this line wherever you need to break
import ipdb; ipdb.set_trace()
- Restart extension
Set up Logger¶
Here’s all you need to do to enable logging for your extension:
import logging
# create an instance of logger at a module level
logger = logging.getLogger(__name__)
# then use these methods in your classes or functions:
logger.error('...')
logger.warn('...')
logger.info('...')
logger.debug('...')
Note
Please take a short survey to help us build greater API and documentation
Extension Migration¶
Migrate from API v1 to v2.0.0¶
API version 2 was introduced along with Ulauncher v5 after migrating from Python 2 to 3.
Required actions:
Remove
manifest_version
frommanifest.json
. It’s no longer neededIn the manifest file rename
api_version
torequired_api_version
Set its value to
^2.0.0
required_api_version
should follow NPM Semver format. In most of the cases you would want to specify a string like^x.y.z
wherex.y.z
is the current version of extension API not Ulauncher app.
- Migrate your extension to Python 3 manually or by using 2to3 tool
- Create a file called
versions.json
in the root directory of master branch using the following content as a template:
[ { "required_api_version": "^1.0.0", "commit": "<branch name with the pre-migration code>" }, { "required_api_version": "^2.0.0", "commit": "<branch name with python3 code>" } ]For more details about
version.json
, see tutorial.For example, you may choose
python2
as a branch name where you keep the old code, which is going to be used by the old Ulauncher app, andmaster
as a branch name where you keep the latest version. In this case the file contents should look like this:[ { "required_api_version": "^1.0.0", "commit": "python2" }, { "required_api_version": "^2.0.0", "commit": "master" } ]
Note
Please take a short survey to help us build greater API and documentation
- Overview
- Understand what Ulauncher extensions are and how they work.
- Development Tutorial
- Create your first extension in under 5 minutes.
- Events
- Events that your extensions can subscribe to and handle.
- Actions
- Actions that your extensions perform in response to events.
- Available Libraries
- List of libraries that you can use in your extensions.
- Examples
- Learn from other Ulauncher extensions.
- Debugging & Logging
- Debugging tips.
- Extension Migration
- How to migrate from one version of Extension API to a new one.