Extensions in GNOME 45

By now it is probably no longer news to many: GNOME Shell moved from GJS’ own custom imports system to standard JavaScript modules (ESM).

Imports? ESM?

JavaScript originated in web browsers to add a bit of interactivity to otherwise static pages. There was no need to split up small code snippets into multiple files, so the language did not provide a mechanism for that.

This did become an issue when people started writing bigger programs in JavaScript, so environments like node.js and GJS added their own import systems to organize code into multiple files. As a consequence, developers and tooling had a hard time transitioning from one environment to another.

That changed in 2015 when ECMAScript 6 standardized modules, resulting in a well-defined, widely-supported syntax supported by all major JavaScript engines. GJS has supported ESModules since 2021, but porting GNOME Shell was a much bigger task that had to be done all at once.

So? Why should I care?

Well, there is a teeny tiny drawback: Modules and legacy imports are incompatible in practice.

Modules are loaded differently than scripts, and some statements — namely import and export — are only valid in modules. That means that trying to import a module with the legacy system will result in a syntax error if the module uses one of those statements (about as likely as a pope being Catholic).

Modules also hide anything to the outside that isn’t explicitly exported. So while it is technically possible to import a script as module, it is about as useful as importing an empty file.

What does this mean for extensions?

Extensions that target older GNOME versions will not work in GNOME 45. Likewise, extensions that are adapted to work with GNOME 45 will not work in older versions.

You can still support more than one GNOME version, but you will have to upload different versions to extensions.gnome.org for pre- and post-45 support.

There is a porting guide with detailed information. The two most important changes (that will be enough for many extensions!) are:

  1. Use standard syntax to import modules from gnome-shell:

    import * as Main from 'resource:///org/gnome/shell/ui/main.js';
  2. Export a default class with enable() and disable() methods from your extension.js.

    You may want to extend the new Extension class that replaces the convenience API from the old ExtensionUtils module.

    import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
    export default class MyTestExtension extends Extension {
        enable() {
            console.log(_('%s is now enabled').format(this.uuid));
        disable() {
            console.log(_('%s is now disabled.').format(this.uuid));

Last but not least, you can always find friendly people on Matrix and Discourse who will be happy to help with any porting issues.

  • Moving from the custom import system from GJS to the industry standard ECMAScript 6 will cause every extension to break. The move though will mean we are following proper standards and not home grown ones allowing greater compatibility with JavaScript ecosystem.
  • Legacy imports are still supported on extensions.gnome.org but you will need to upload a pre and post GNOME 45 support in order support both LTS and regular distribtuions.

For GNOME Extension Developers:
There is a active extension community who can help you port to the new import system at Matrix and Discourse who can help you quickly port to the new version.

You can test your extensions by downloading the latest GNOME OS and trying your extension there.

To the GNOME Community:
Please file bugs with your favorite extensions or have a friendly conversation with your extension writers so that we can help minimize the impact of this change. Ideally, you could help with the port and provide a pull or merge request to help maintainers.


GNOME Shell styling changes: A PSA for theme authors

TL;DR: The gnome-shell-sass repository is no longer getting updated upstream.


As gnome-shell’s CSS grew more complex, designers needed something more expressive, so they started compiling the stylesheet from SASS. The sources were moved to a subproject, so they could be shared between the regular stylesheet and the GNOME Classic stylesheet.

Fast-forward to the present day, gnome-shell now includes a light variant to support the global color-scheme option.

GNOME Shell in light style

GNOME Classic has been updated to use that built-in support instead of compiling a separate stylesheet on its own.

That means that the gnome-shell-sass repository is no longer needed by the gnome-shell-extensions project, and will therefore no longer be updated upstream.

If you are building your own theme from those sources, you will either have to get them from the gnome-shell repository yourself, or coordinate with other 3rd party theme authors to keep the subproject updated.

GNOME 40 & your extension

As you are probably aware by now, GNOME 40 will bring some big changes.

This is exciting, but these changes also means that some extensions will have to adjust to continue working in GNOME 40.

To help with that, this post provides a brief overview(!) of the most important changes.

You can join the #gnome-shell and #shell-extensions channels on IRC/Matrix for further questions, and the friendly folks of the extensions rebooted project provide helpful resources like a testing VM image as well as advice.


The overview was the focus of the GNOME 40 changes, so it is not surprising that it is also the place where adjustment is most likely to be needed.


This is now the central place that controls the overall state and ties the various overview components together:

    • dash (now horizontal and at the bottom, otherwise largely the same as before)
    • window picker
    • app grid
    • workspace minimap (formerly known as workspace switcher)
    • search controller (formerly known as view selector)

All those components have seen changes to their internals as well, so watch out for those if your extension modifies any of them.

Adjustments, adjustments, adjustments

Most state is now controlled by adjustments, so that transitions can either be animated or controlled by gestures:

    • overview adjustment
      controls the overall overview state, with the possible ControlsState values HIDDEN, WINDOW_PICKER and APP_GRID
    • fit-mode adjustment
      controls how workspaces are displayed, namely whether centering on a single workspace (0) or fitting all workspaces in the available space (1)
    • workspace adjustment
      controls which workspace is in view, that is the value corresponds to the active workspace (or an in-between value during transitions)
    • app grid adjustment
      controls the scroll position of app grid pages
    • workspace state adjustment
      controls whether window previews are shown floating (0) as outside the overview, or spread out according to the used layout strategy (1)

The first one is the most important one, driving both the overview transition and the fit-mode and workspace-state adjustments.

Backgrounds have moved into workspaces

This is a relatively minor change, but it affected two extensions I’m maintaining, so I decided it was worth mentioning after all.


Extension preferences must use GTK4 now.

It is not possible to use both GTK3 and GTK4 from the same process, so we all have to take the plunge together; and as the process that opens preference dialogs was ported, now is an excellent time for that 🙂

The GTK documentation contains a migration guide that lists most of the changes that are required.

Porting a single preference dialog should be a lot easier than porting an entire application. At least that’s what I found when porting the gnome-shell-extensions and Fedora’s background-logo extensions, so hopefully it won’t be much more work for you.

Version validation

With all those changes, we expect more extensions to have compatibility issues than usual.

To protect against that, we are again doing version validation. That means unless the shell-version field in an extension’s metainfo.json file includes “40”, it will be disabled and marked as out-of-date.

Apropos “version”: We are following the new GNOME version scheme, so if you do any version comparisons yourself, make sure to take the major version into account.

… and one more thing

We no longer put arrows in top bar menus.

There have been no significant changes to top bar menus this cycle, so if your extension just adds a menu or indicator, it is unlikely to break.

It will just look a bit foreign if you show an arrow next to your menu, so we recommend removing them.