Nautilus, also known as “Files”, is the default file manager of the GNOME desktop environment. In a previous tutorial we saw how to create and call custom scripts from the Nautilus context-menu: this feature can be really useful but is somehow limited. By installing the nautilus-python package in our favorite Linux distribution, and writing just few lines of Python code, we can overcome such limitations and create proper Nautilus extensions.
In this tutorial we learn how to write Nautilus extensions using Python and the bindings provided by the nautilus-python package.
In this tutorial you will learn:
- How to install nautilus-python on some of the most used Linux distributions
- How Nautilus API works
- How to create Nautilus extensions in Python

Category | Requirements, Conventions or Software Version Used |
---|---|
System | Distribution independent |
Software | Nautilus/Files, nautilus-python, Python3 |
Other | Administrative privileges to install packages, basic knowledge of Python and Object Oriented Programming |
Conventions | # – requires given linux-commands to be executed with root privileges either directly as a root user or by use of sudo command $ – requires given linux-commands to be executed as a regular non-privileged user |
Installation
To be able to create Nautilus extensions using Python, the first thing we need to do is to install the nautilus-python
package. The software is itself an extension which provides Python bindings for Nautilus. To install the package on Fedora we can launch the following command:
$ sudo dnf install nautilus-python
To perform the installation on Debian and other Debian-based Linux distribution, instead:
$ sudo apt install nautilus-python
The package is available also in the Archlinux “Community” repository. We can install it using pacman
:
$ sudo pacman -Sy python-nautilus
Extensions API overview
Once the Python bindings for Nautilus are installed, we can start writing our first extensions. In the course of this article I will use the latest stable release of Fedora (36 at the moment of writing), which comes with version 42.2
of the file manager. Keep in mind that API may change, so the code you see in this article may require some adjustments in the future.
We can extend Nautilus in many ways: we can create custom actions which can be executed from the file manager context menu when certain type of files are selected, for example, but we can also display additional information about a file in dedicated property pages or in custom columns displayed in the Nautilus “list” view. Depending on what we want to accomplish, the class which is going to represent our extension, should inherit from one or more of the following base classes:
- Nautilus.ColumnProvider
- Nautilus.FileInfo
- Nautilus.InfoProvider
- Nautilus.LocationWidgetProvider
- Nautilus.MenuProvider
- Nautilus.PropertyPageProvider
The APIs provide also a series of classes which represent some elements we can define:
Let’s see some practical examples of how we can use them to create our own Nautilus extensions.
Creating a context-menu extension
In this first example we create a custom-action extension. This extension adds a custom action which allows us to convert jpeg and png images to the webp format, directly from the Nautilus context-menu. Here is how the extension is defined:
"""
Add an action to the Nautilus context-menu to convert png and jpeg images
to the wepb format.
Author: Egidio Docile
"""
from urllib.parse import urlparse, unquote
from gi.repository import GObject, Nautilus
from PIL import Image
class ConvertToWebpMenuProvider(GObject.GObject, Nautilus.MenuProvider):
VALID_MIMETYPES = ('image/png', 'image/jpeg')
def convert(self, menu, files):
for file in files:
file_path = unquote(urlparse(file.get_uri()).path)
image = Image.open(file_path)
image.save(f"{file_path}.webp", format="webp")
def get_file_items(self, window, files):
for file in files:
if file.get_mime_type() not in self.VALID_MIMETYPES:
return ()
menu_item = Nautilus.MenuItem(
name="convert_to_webp",
label="Convert to wepb")
menu_item.connect('activate', self.convert, files)
return menu_item,
The first thing we did was to import needed modules and functions. From the urllib.parse
module we imported the urlunquote
and urlparse
functions (we will see why in a moment), than, from gi.repository
we imported the GObject
and Nautilus
modules.
Our extension is represented by the ConvertToWebpMenuProvider
class which is derived from GObject.GObject
(all extensions must extend this class) and Nautilus.MenuProvider
. The get_file_items
method we implemented in the class is inherited from the latter and accepts two arguments which are automatically passed to it.
The first one,
window
, is an object which represents the parent Gtk window, the second, files
, is a list of FileInfo
objects, each one representing a selected file. The method must return a list of Nautilus.MenuItem
objects which represent the additional menu items we want to add to the Nautilus context menu (our implementation actually returns a tuple).
In the method body we checked if any of the selected files has a MIME type which is not among the accepted ones: image/png
and image/jpeg
. The MIME type of a file is returned by the get_mime_type
method of the FileInfo
object which represents it. If some of the selected files has a MIME type which is not allowed, we return an empty tuple: in such case, no extra entries will appear in the Nautilus context menu. If all files have a valid MIME type, instead, we create an instance of the Nautilus.MenuItem
class. The constructor of this class accepts four strings as arguments:
- name: the menu item identifier
- label: the label the user sees in the Nautilus context-menu
- tip: the tooltip for the menu entry
- icon: the name of the icon to be displayed in the menu entry
In our example we used only the first two arguments and omitted the last two, which are optional.
Once the menu item object is created, we call the connect
method on it in order to associate a callback to the activate
signal, which is emitted when the user clicks on the menu entry produced by our extension. The first argument passed to the connect
method is the name of the signal, the second is the name of the callback function; the subsequent ones are the arguments which will be passed to the callback itself. In the example we used the convert
method of our class as callback, and passed the list of the selected files as argument to it.
The convert
method is where the image conversion is actually performed. How we converted the image is not important. What should be noticed is that we obtained the URI of the FileInfo
object by using the get_uri
method. Since we need just the “path” part of the URI, we passed it as argument to the urlparse
function. This function parses the URI into 6 parts which can be accessed via the corresponding properties of the returned ParseResult
object:
We further processed the path by passing it to the unquote
function, which replaces %xx
escapes by their single character equivalents. This is especially useful in case the path contains spaces.
The file containing the Python code for our extension must be copied in the ~/.local/share/nautilus-python/extensions
directory (it must be created if doesn’t already exist). Here is how our custom menu action is displayed when we select one or more jpeg or png images and we open the Nautilus context menu:

Creating a “property” extension
Let’s see another example. This time we will create an extension which provides an additional “tab” in the property section of epub
files. We will use this space to display some book metadata:
"""
Add a Nautilus property page to epub files to display book metadata.
Author: Egidio Docile
"""
from gi.repository import GObject, Gtk, Nautilus
from urllib.parse import urlparse, unquote
import epub_meta
class EpubMetadataPropertyPageProvider(GObject.GObject, Nautilus.PropertyPageProvider):
def _get_epub_metadata(self, file):
file_path = unquote(urlparse(file.get_uri()).path)
return epub_meta.get_epub_metadata(file_path)
def get_property_pages(self, files):
if len(files) != 1:
return
file = files[0]
if file.get_mime_type() != 'application/epub+zip':
return ()
metadata = self._get_epub_metadata(file)
property_label = Gtk.Label('Epub Metadata')
property_label.show()
grid = Gtk.Grid()
grid.props.margin_left = 50
grid.props.margin_top = 20
for row, key in enumerate(['authors', 'publisher', 'title']):
val = ",".join(metadata[key]) if isinstance(metadata[key], list) else metadata[key]
grid.attach(Gtk.Label(f'{key.capitalize()}: ', xalign=1), 0, row, 1, 1)
grid.attach(Gtk.Label(val, xalign=0), 1, row, 1, 1)
grid.show_all()
page = Nautilus.PropertyPage(
name="epub_metadata",
label=property_label,
page=grid)
return page,
In this example you can see that from gi.repository
we imported also the Gtk
module. We did this in order to create a grid container to order and display the ebook metadata.
The get_property_pages
method we implemented in the EpubMetadataPropertyPageProvider
class, is inherited from the Nautilus.PropertyPageProvider
base class. It accepts the list of the FileInfo
objects representing the selected files as sole argument and returns the list of PropertyPage
objects which should be added to the relevant Nautilus section.
The first thing we did in the method body was to test if only one file has been selected, since we want our extension to work only on one file at a time. If the user selects multiple files, we just return an empty tuple, just like we did in the previous example.
If just one file is selected, we make sure it is an epub by checking its MIME type, and eventually we retrieve its metadata (authors, publisher and title). In order to display the data we created a Gtk.Grid object, and we used Gtk.Label objects to show the text.
Finally, we created a Nautilus.PropertyPage
object, and returned a tuple containing it as the only element. Now, when we right click on an epub
file, and select “Properties” in the Nautilus context-menu, we should see an additional property tab, “Epub Metadata”:

Here I didn’t explained in detail how we created the widget, since I consider this to be an implementation detail (and it should be refined). The important thing, in this context, is to understand the main logic behind the extension creation.
Creating a column extension
In our third and final example, we create a “column” extension. Our extension adds a new column containing file permissions displayed in octal form to the Nautilus “list” view. Here is a possible implementation:
"""
Add a column containing the file permissions in octal notation
to the Nautilus list view.
Author: Egidio Docile
"""
import os
from urllib.parse import unquote, urlparse
from gi.repository import GObject, Nautilus
class OctalPermissionsInfoProvider(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider):
def get_columns(self):
column = Nautilus.Column(
name="file_permissions_octal",
attribute="file_permissions_octal",
label="Permissions (octal)",
description="The octal file permissions"
)
return column,
def update_file_info(self, file):
file_path = unquote(urlparse(file.get_uri()).path)
octal_permissions = oct(os.stat(file_path).st_mode)[-3:]
file.add_string_attribute('file_permissions_octal', octal_permissions)
In this example, our extension is represented by the OctalPermissionsInfoProvider
class, which as usual, inherits from GObject.GObject
, and this time, from other two base classes: Nautilus.ColumnProvider
and Nautilus.InfoProvider
.
Of the former we implemented the get_columns
method which must return the list of the additional columns which should be added to Nautilus. In the method body we created just one column, represented as a Nautilus.Column
object and returned it as the sole element of a tuple.
To create the
Nautilus.Column
object we passed four strings as arguments to its constructor. The first one, name
, is used as the column identifier; the second, attribute
, is the name of the attribute which should be displayed in the column (the file_permissions_octal attribute is added to the FileInfo
object representing the file in the update_file_info
method). The third argument, label
, is the text displayed to the user as the column header, and the last one, description
is the description of the column.
The update_file_info
method is inherited from the Nautilus.InfoProvider
base class; it takes a single FileInfo
object as argument. In our implementation we retrieved the permissions of the file and transformed them in octal form; than, we set the result as the value of the file_permissions_octal attribute we added to the object using its add_string_attribute
method.
Here is how the additional column is displayed in the Nautilus “list” view (if the column is not displayed by default you should right-click on the columns header, than check the related entry in the columns menu):

Conclusions
In this tutorial we learned how to create Nautilus extensions using Python, using the bindings provided by the nautilus-python package. We saw how to install the package in some of the most used Linux distributions, we took a look at the Nautilus API, and we created some extensions as examples. To know more about the Nautilus 3.0 API, you can take a look at the following documentation, while to follow the development of nautilus-pytthon you can follow the project on gitlab.