Making a modern GNOME Shell Extension, part 1

All of the code in this article is designed for GNOME Shell 3.2.1

Our basic skeleton

With now active, I think it’s time to take a fresh look at the GNOME Shell Extension system and discover how to make a good GNOME Shell Extension. I’m going to start with two files that I’ll call our extension skeleton.

Let’s start the development process from scratch, ignoring fancy things like gnome-shell-extension-tool

$ mkdir -p ~/.local/share/gnome-shell/extensions/

When GNOME Shell starts up, it scans all the directories specified in XDG_DATA_HOME and XDG_DATA_DIRS and loads the extensions in them. Extensions are placed in gnome-shell/extensions/${UUID} relative to one of these directories. An extension is a directory containing at least extension.js and metadata.json. Let’s take a look at extension.js.

function init(meta) {

function enable() {

function disable() {

This is the most basic extension.js file that you can create. It does nothing. Let’s install it:

$ cp extension.js ~/.local/share/gnome-shell/extensions/

And the metadata.json:

“name”: “Basic GNOME Shell Extension Example”,
“uuid”: “”,
“description”: “A basic GNOME Shell Extension made for an example”,
“url”: “”,
“shell-version”: [ “3.2” ]

The name, uuid, and shell-version keys are required. The url and description keys are encouraged.

$ cp metadata.json ~/.local/share/gnome-shell/extensions/

At this point, you should be able to restart GNOME Shell, and GNOME Shell should pick up the extension. Each extension starts in the “DISABLED” state. To enable the extension, modify the ‘enabled-extensions’ GSettings key in the ‘’ schema.

$ gsettings set enabled-extensions "['']"

GNOME Shell should enable the extension, which you can verify by using the Looking Glass. Of course, the extension is quite useless, as it does nothing.

Doing something

GNOME Shell 3.2 requires you to have at least an init() function in the module. This init() function will probably do nothing in most extensions. The init() function is guaranteed to be called at most once in a GNOME Shell session. The init() function is guaranteed to be called before either of the two main extension points, enable() or disable(), are called. Use init() to do any global initialization that needs to be done once, like binding a gettext domain:

const Gettext = imports.gettext;

function init(meta) {
    Gettext.bindtextdomain('basic-extension-example', '/home/jstpierre/.local/share/gnome-shell/extensions/');

Of course, hardcoding a path like this is a bad idea. Let’s introduce the meta object. You may have seen the ‘meta’ parameter that was passed to init(). This is known as the ‘meta object’. It contains all the properties specified in your metadata.json, among more.

const Gettext = imports.gettext;

function init(meta) {
    let localeDir = meta.dir.get_child('locale');
    Gettext.bindtextdomain('basic-extension-example', localeDir.get_path());

So, what about enable() and disable()? Here are the facts:

  1. They can be called at any time, any number of times, but only in pairs. The Shell will never call the same method twice, so you do not have to do your own bookkeeping.
  2. enable() should make your extension add its custom functionality, and disable() should remove the custom functionality.
  3. If your extension’s UUID is in the ‘enabled-extensions’ key, and the user restarts the Shell, init() will be called, followed by enable().
  4. If your extension’s UUID is not in the value when the user restarts the Shell, your code will not be loaded. If your extension is in ‘enabled-extensions’, and the user disables your extension by using any number of things that modify the ‘enabled-extensions’ key, like SweetTooth or gnome-tweak-tool, the disable() method will be called.

Next time, we’ll make our simple sample extension do something.

Leave a Reply

Your email address will not be published. Required fields are marked *