Skip to content

Port Extensions to GNOME Shell 45


GNOME Shell 45 moved to ESM (ECMAScript modules). That means you MUST use the standard import declaration instead of relying on the previous imports.* approach.

If you are not familiar with ESM yet, please read the MDN guide to learn about modules in general. For GNOME Shell extensions, modules can be split into 3 categories:

  1. GI libraries are imported as defaults with a module specifier using a gi:// URI scheme. So import them with import SOME_NAME from 'gi://LIBRARY_NAME'.
  2. GNOME Shell files are imported as resources with a resource:// URI scheme. See the examples below.
  3. Your own files are imported with relative paths.

Here're some examples of importing modules in the old and the new way:

  • importing GObject-Introspection generated (gi) libraries

    // Before GNOME 45
    const GLib =;
    // GNOME 45
    import GLib from 'gi://GLib';
  • importing versioned gi libraries

    // Before GNOME 45 = '3.0';
    const Soup =;
    // GNOME 45
    import Soup from 'gi://Soup?version=3.0';
  • importing native GNOME Shell modules (e.g., from the ui directory)

    // Before GNOME 45
    const Main = imports.ui.main;
    // GNOME 45
    import * as Main from 'resource:///org/gnome/shell/ui/main.js';
  • importing native GNOME Shell modules (e.g., from the misc directory)

    // Before GNOME 45
    const Util = imports.misc.util;
    // GNOME 45
    import * as Util from 'resource:///org/gnome/shell/misc/util.js';
  • importing parts of a module

    // Before GNOME 45
    const {panel, wm} = imports.ui.main;
    // GNOME 45
    import {panel, wm} from 'resource:///org/gnome/shell/ui/main.js';
  • importing your own modules that are part of your extension's code base

    // Before GNOME 45
    const Me = imports.misc.extensionUtils.getCurrentExtension();
    const MyModule = Me.imports.MyModule;
    // GNOME 45
    import * as MyModule from './MyModule.js';

extension.js vs prefs.js

The GNOME Shell resource path to be used while importing from within prefs.js is a bit different compared to imports in extension.js. In prefs.js, resource paths start with resource:///org/gnome/Shell/Extensions/js/, while in the extension.js case, they start with resource:///org/gnome/shell/.

For example, here's how you'd import the misc/config module:

  • In extension.js:

    import * as Config from 'resource:///org/gnome/shell/misc/config.js';
  • in prefs.js (please note the path is case sensitive):

    import * as Config from 'resource:///org/gnome/Shell/Extensions/js/misc/config.js';



Since ESM files contain import and export keywords, your extension modules won't be compatible with older GNOME Shell versions. You should remove the old shell versions and only use 45 in shell-version in your metadata.json!

The good news is that e.g.o supports multi versioning - you can still submit multiple packages with different shell versions.


extensionUtils no longer contains helper functions extensions usually use. Instead, you can use Extension's and ExtensionBase's properties and methods.

Additionally, the extension module is offering translation functions (gettext, ngettext and pgettext).

For example, here we use getSettings() and gettext():

import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';

export default class MyTestExtension extends Extension {
    enable() {
        this._settings = this.getSettings();
        console.log(_('This is a translatable text'));

    disable() {
        this._settings = null;

If subclassing Extension and ExtensionPreferences, you can lookup the extension object from any module by using the static methods:

import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';

let extensionObject, extensionSettings;

// Getting the extension object by UUID
extensionObject = Extension.lookupByUUID('');
extensionSettings = extensionObject.getSettings();

// Getting the extension object by URL
extensionObject = Extension.lookupByURL(import.meta.url);
extensionSettings = extensionObject.getSettings();

The properties and methods you can use:

initTranslations()nullConsider this method deprecated. Only specify gettext-domain in metadata.json. GNOME Shell can automatically initiate the translation for you when it sees the gettext-domain key in metadata.json.
getSettings()Gio.SettingsStill can read settings-schema from metadata.json.
openPreferences()nullOpens the preferences window if your extension has one.
uuidstringExtension's UUID value
dirGio.FileExtension's directory path as an instance of Gio.File
pathstringExtension's directory path as a string
metadataobjectMetadata object built from metadata.json


extension.js MUST export a default class containing enable() and disable() methods:

export default class MyTestExtension {
    enable() {

    disable() {


If your extension is using prefs.js, you should export a default class extending ExtensionPreferences from the prefs module with fillPreferencesWindow method.

All ExtensionBase's properties and methods mentioned before can be used here as well.

Just like the extension module, the prefs module is also offering translation functions.

import Adw from 'gi://Adw';

import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';

export default class MyExtensionPreferences extends ExtensionPreferences {
    fillPreferencesWindow(window) {
        window._settings = this.getSettings();

        const page = new Adw.PreferencesPage();

        const group = new Adw.PreferencesGroup({
            title: _('Group Title'),




misc.animationUtils is a new module in GNOME Shell 45 that offers some animation convenience tools.

Examples include:

  • wiggle() - can animate an actor (e.g., St.Entry) in X axis
  • adjustAnimationTime() - can change the animation time
  • and more...


GNOME Shell 45 adds Math.clamp() function. You can clamp numbers between some min and max values. This is only available in extension.js, and not in prefs.js.



addClickAction() is a new method for draggable. It allows you to add click action to your draggable actor.

There is a new app property in MprisPlayer that gives you Shell.App or null. It can be used to get the current player app.


You can now get the searchController instance by /ui/main.js/overview.searchController.

SearchController Provider

SearchController now offers addProvider() and removeProvider() so you can add and remove the search provider objects easier.

Top Panel

Panel.toggleQuickSettings() addition

GNOME Shell 45 adds toggleQuickSettings() to the panel. You can toggle quick settings menu via ui.main.panel.toggleQuickSettings().

Panel.toggleAppMenu() removal

GNOME Shell 45 removs Panel.toggleAppMenu() since the keyboard shortcut for app menu has been removed.


BackgroundAppMenuItem uses spinner animation that can start spinning when quitting the app represented by the BackgroundAppMenuItem instance.

Created In/ui/status/backgroundApps.js/BackgroundAppMenuItem._init()
Style Class.spinner


ui.status.backlight is a new section in quick settings that allows you to control the keyboard backlight.

Direct Access/ui/main.js/panel.statusArea.quickSettings._backlight
Created In/ui/panel.js/QuickSettings._init()
Style Class.keyboard-brightness-item

Camera is a new indicator to show when user's camera device is in use.

Direct Access/ui/main.js/panel.statusArea.quickSettings._camera
Created In/ui/panel.js/QuickSettings._init()
Style Class.privacy-indicator


ActivitiesButton no longer has a label. Instead, it uses WorkspaceIndicators as its child.

Implementation Path/ui/panel.js/WorkspaceIndicators
Direct Access/ui/main.js/panel.statusArea.activities.get_first_child()
Created In/ui/panel.js/ActivitiesButton._init()
Style Class#panelActivities StBoxLayout

The dots inside WorkspaceIndicators are instances of WorkspaceDot. WorkspaceDot uses .scaleIn() and .scaleOutAndDestroy() to animate the dots when workspaces are being added or removed:

Implementation Path/ui/panel.js/WorkspaceDot
Style Class.workspace-dot (#panelActivities .workspace-dot)

There is also /ui/main.js/panel.INACTIVE_WORKSPACE_DOT_SCALE for inactive workspace dots.



MAX_THUMBNAIL_SCALE is a const and no longer can be changed. ThumbnailsBox._maxThumbnailScale is a new property that allows you to change the max thumbnail scale size.

Implementation Path/ui/workspaceThumbnail.js/ThumbnailsBox
Direct Access/ui/main.js/overview._overview._controls._thumbnailsBox._maxThumbnailScale

Clutter and Mutter


When using the event objects in vfuncs and signals, use the Clutter.Eventgetters instead of the fields directly. See merge request mutter!3163, which introduces the relevant changes, and merge request gnome-shell!2872, which adapts to the changes.


Meta.Rectangle should be replaced with Mtk.Rectangle. See merge request mutter!3128 for background information. For compatibility, Meta.Rectangle has temporarily been aliased to a function, which returns a Mtk.Rectangle (See commit gnome-shell@f1317f07).


log() is just an alias for console.log() now and you no longer can filter journald by GNOME_SHELL_EXTENSION_UUID and GNOME_SHELL_EXTENSION_NAME.

console.log() isn't new in GNOME Shell 45 but if you are still using log() for different log levels, you should use console.* functions instead:

  • console.debug()
  • console.error()
  • console.log()
  • console.warn()



There were no relevant changes to GJS in GNOME 45.

MIT Licensed | GJS, A GNOME Project