GNOME Shell Extensions Review Guidelines

These are the guidelines for developers who would like their extensions distributed on egoopen in new window. Submissions that fail to meet the requirements will be rejected during the review period.

Extensions are reviewed carefully for malicious code, malware and security risks, but not for bugs or issues. It is the responsibility of the developer to test an extension's functionality before submission.

If this is your first time writing an extension, please see the documentation available on gjs.guideopen in new window.

Basics

General Advice

  • Write clean code, with consistent indentation and style
  • Test a clean install of your extension before uploading
  • Don't use classes or methods from the deprecated Lang module

Guidelines

  • Only use init() to initialize Gettext translations and static resources
  • Use enable() to start creating objects, connecting signals
  • Use disable() to revert any changes, disconnect signals and destroy objects
  • Only spawn subprocesses as a last resort and never synchronously

Rules

Only use init() for initialization

init() is called by GNOME Shell when your extension is loaded, not when it is enabled. Your extension MUST NOT create any objects, connect any signals, add any main loop sources or modify GNOME Shell here.

As a rule, init() should ONLY be used for operations that can only be done once and can not be undone. Most extensions should only use init() to initialize Gettext translations:

const ExtensionUtils = imports.misc.extensionUtils;


function init() {
    ExtensionUtils.initTranslations();
}

function enable() {
    // Create objects, connect signals, create timeout sources, etc.
}

function disable() {
    // Destroy objects, disconnect signals, remove timeout sources, etc.
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

If using the Extension() object pattern, this extends to the constructor() of the Extension class:

const ExtensionUtils = imports.misc.extensionUtils;


class Extension {
    constructor() {
        // DO NOT create objects, connect signals or add main loop sources here
    }

    enable() {
        // Create objects, connect signals, create timeout sources, etc.
    }

    disable() {
        // Destroy objects, disconnect signals, remove timeout sources, etc.
    }
}

function init() {
    // Initialize translations before returning the extension object
    ExtensionUtils.initTranslations();

    return new Extension();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Destroy all objects

Any objects or widgets created by an extension MUST be destroyed in disable():

const St = imports.gi.St;

let widget = null;

function init() {
}

function enable() {
    widget = new St.Widget();
}

function disable() {
    if (widget) {
        widget.destroy();
        widget = null;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Disconnect all signals

Any signal connections made by an extension MUST be disconnected in disable():

let handlerId = null;

function init() {
}

function enable() {
    handlerId = global.settings.connect('changed::favorite-apps', () => {
        log('app favorites changed');
    });
}

function disable() {
    if (handlerId) {
        global.settings.disconnect(handlerId);
        handlerId = null;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Remove main loop sources

Any main loop sources created MUST be removed in disable():

const GLib = imports.gi.GLib;

let sourceId = null;

function init() {
}

function enable() {
    sourceId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 5, () => {
        log('Source triggered');

        return GLib.SOURCE_CONTINUE;
    });
}

function disable() {
    if (sourceId) {
        GLib.Source.remove(sourceId);
        sourceId = null;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

You MUST remove all active main loop sources in disable(), even if the callback function will eventually return false or GLib.SOURCE_REMOVE.

Code must not be obfuscated

Extension code MUST be readable and reviewable JavaScript.

Although a specific code-style is not enforced during review, it is expected that your code is formatted in a way that can be understood by reviewers. Specifically, the following rules MUST be adhered to:

  • JavaScript code must be legible and reasonably structured
  • JavaScript code must not be minified or obfuscated
  • TypeScript must be transpiled to well-formatted JavaScript

No excessive logging

Extension MUST NOT print excessively to the log. The log should only be used for important messages and errors.

If a reviewer determines that an extension is writing excessively to the log, the extension will be rejected.

Scripts and Binaries

Use of external scripts and binaries is strongly discouraged. In cases where this is unavoidable for the extension to serve it's purpose, the following rules must be adhered to:

  • Extensions MUST NOT include binary executables or libraries
  • Processes MUST be spawned carefully and exit cleanly
  • Scripts must be written in GJS, unless absolutely necessary
  • Scripts must be short, simple and distributed under an OSI approved license

Reviewing Python modules, HTML, and web JavaScriptopen in new window dependencies is out of scope for extensions.gnome.org. Unless required functionality is only available in another scripting language, scripts must be written in GJS.

Extensions may install modules from well-known services such as pip, npm or yarn but MUST require explicit user action. For example, the extension preferences may include a page which describes the modules to be installed with a button.

Privileged Subprocess must not be user-writable

Spawning privileged subprocesses should be avoided at all costs.

If absolutely necessary, the subprocess MUST be run with pkexec and MUST NOT be an executable or script that can be modified by a user process.

Extensions must be functional

Extensions are reviewed, but not always tested for functionality so an extension MAY be approved with broken functionality or inoperable preferences window.

However, if an extension is tested and found to be fundamentally broken it will be rejected. Extensions which serve no purpose or have no functionality will also be rejected.

metadata.json must be well-formed

The metadata.json file that ships with every extension should be well-formed and accurately reflect the extension.

KeyDescription
nameThis should not conflict with another extension if possible. If it is a fork of another extension it MUST have a unique name to distinguish it.
uuidThis must be of the form extension-id@user-domain. extension-id and user-domain MUST only contain numbers, letters, period (.), underscore (_) and dash (-). user-domain MUST be a domain or namespace you control and be separated from extension-id with an @ symbol.
descriptionThis should be a reasonable length, but may contain a few paragraphs separated with \n literals or a bullet point list made with * characters.
versionThis must be a whole number like 2, not a string like "2" or semantic version like 2.1.
shell-versionThis MUST only contain stable releases and up to one development release. Extensions must not claim to support future GNOME Shell versions. As of GNOME 40, an entry may simply be a major version like 40 to cover the entire release.
urlThis should be a link to a Github or GitLabopen in new window repository where users can report problems and learn more about your extension.
session-modesThis MUST be dropped if you are only using user mode. The only valid values are user and unlock-dialog.

Example:

{
    "uuid": "color-button@my-account.github.io",
    "name": "ColorButton",
    "description": "ColorButton adds a colored button to the panel.\n\nIt is a fork of MonochromeButton.",
    "version": 1,
    "shell-version": [ "3.38", "40", "41.alpha" ],
    "url": "https://github.com/my-account/color-button",
    "session-modes":  [ "unlock-dialog", "user" ]
}
1
2
3
4
5
6
7
8
9

Session Modes

In rare cases, it is necessary for an extension to continue running while the screen is locked. In order to be approved to use the unlock-dialog session mode:

  • It MUST be necessary for the extension to operate correctly.
  • All signals related to keyboard events MUST be disconnected in unlock-dialog session mode.
  • The disable() function MUST have a comment explaining why you are using unlock-dialog.

Extensions MUST NOT disable selectively.

GSettings Schemas

For extensions that include a GSettings Schema:

  • The Schema ID MUST use org.gnome.shell.extensions as a base ID.
  • The Schema path MUST use /org/gnome/shell/extensions as a base path.
  • The Schema XML file MUST be included in the extension ZIP file.
  • The Schema XML filename MUST follow pattern of <schema-id>.gschema.xml.

Licensing

GNOME Shell is licensed under the terms of the GPL-2.0-or-later, which means that derived works like extensions MUST be distributed under compatible terms (eg. GPL-2.0-or-later, GPL-3.0-or-later).

While your extension may include code licensed under a permissive license such as BSD/MIT, you are still approving GNOME to distribute it under terms compatible with the GPL-2.0-or-later.

If your extension contains code from another extension it MUST include attribution to the original author in the distributed files. Not doing so is a license violation and your extension will be rejected.

Copyrights and Trademarks

Extensions MUST NOT include copyrighted or trademarked content without proof of express permission from the owner. Examples include:

  • Brand Names and Phrases
  • Logos and Artwork
  • Audio, Video or Multimedia

Recommendations

Don't include unnecessary files

Extension submissions should not include files that are not necessary for it to function. Examples include:

  • build or install scripts
  • .po and .pot files
  • unused icons, images or other media

A reviewer MAY decide to reject an extension which includes an unreasonable amount of unnecessary data.

Use a linter

Using ESLintopen in new window to check your code can catch syntax errors and other mistakes before submission, as well as enforce consistent code style. You can find the ESLint rules used by GNOME Shell on GitLabopen in new window.

Following a specific code style is not a requirement for approval, but if the codebase of an extension is too messy to properly review it MAY be rejected. This includes obfuscators and transpilers used with TypeScript.

UI Design

Although not required for approval, it is recommended that extension preferences follow the GNOME Human Interface Guidelinesopen in new window to improve consistency with the GNOME desktop.

Getting Help

There are several ways for you to get help with your extension.

Last Updated: 5/4/2023, 6:05:32 AM
Contributors: Javad Rahmatzadeh, Javad Rahmatzadeh, Andy Holmes, Florian Müllner