A QGIS plugin to create, manage and apply named interface configurations called workspaces. Switch between different working environments in one click.
- Why QWorkspace Switcher?
- Features
- Installation
- Usage
- Plugin Integration
- Configuration File
- Architecture
- License
- Author
- Contributing
- Related Projects
QGIS is a powerful GIS platform, but its interface can be overwhelming — especially for users who switch between different tasks during the same working session (data collection, hydraulic modeling, results analysis...).
The native QGIS customization options (profiles, manual toolbar toggling) are insufficient for fast context switching:
- Changing a profile requires restarting QGIS entirely
- Manual reorganization is repetitive and time-consuming
- No named configurations can be saved and reapplied
QWorkspace Switcher solves this by letting you define dedicated interface configurations for each workflow and switch between them instantly from the toolbar.
Originally developed at CNR (Compagnie Nationale du Rhône) for hydraulic modeling workflows, QWorkspace Switcher is designed as a generic, domain-independent tool for any QGIS user or organization.
- Create workspaces by capturing the current QGIS interface state in one click
- Apply a workspace instantly from the toolbar button
- Configure each button style:
text— text onlyicon— icon onlyicon_text— icon on the left + texttext_icon— text on the left + icon on the right
- Custom icon per workspace (PNG, SVG, JPG, ICO)
- Dropdown menu per workspace — quick access to plugin menus
- Show/hide the QGIS menu bar per workspace for a clean application-like experience
- Declarative
.psp.jsonsystem — third-party plugins can declare their own recommended workspaces - Blacklist system — deleted plugin workspaces stay deleted across sessions
- Export/import workspace configuration file
- Emergency shortcut
Ctrl+Shift+Mto restore the QGIS menu bar at any time
Plugins → Manage and Install Plugins →
Search "QWorkspace Switcher" → Install
- Download the latest release from GitHub Releases
- Extract to your QGIS plugins folder:
- Windows:
%APPDATA%\QGIS\QGIS3\profiles\default\python\plugins\ - Linux:
~/.local/share/QGIS/QGIS3/profiles/default/python/plugins/ - macOS:
~/Library/Application Support/QGIS/QGIS3/profiles/default/python/plugins/
- Windows:
- Restart QGIS
- Enable the plugin:
Plugins → Manage and Install Plugins → Installed
- QGIS 3.0 or later
- Python 3.x
- PyQt5 (included with QGIS)
- Configure your QGIS interface manually (show/hide panels and toolbars as desired)
- Click the ⚙ button in the QWorkspace Switcher toolbar
- Click + to create a new workspace
- The current interface state is captured automatically
- Configure the button style and icon if needed
- Click Save
Click the workspace button in the toolbar — the interface switches instantly. The active workspace button appears pressed.
- Open the management window (⚙ button)
- Select the workspace in the list
- Modify panels, toolbars and menus via the three tabs:
- Panels — select and position
QDockWidget - Toolbars — select and organize
QToolBarby line - Menus — select plugin menus for the dropdown button
- Panels — select and position
- Click Save
- Select the workspace in the list
- Edit the name in the name field
- Click Save — the workspace is renamed automatically
Select a workspace and click Duplicate to create a copy under a new name.
Click Export to save user.psp.json to a chosen location.
This file can be shared between team members to standardize
working environments.
If the QGIS menu bar is hidden by a workspace, press Ctrl+Shift+M to restore it at any time.
QWorkspace Switcher is designed to work seamlessly with third-party plugins. There are two levels of integration.
QWorkspace Switcher automatically detects your plugin's widgets using three successive strategies:
| Strategy | What it looks for | Reliability |
|---|---|---|
| 1 | self.docks attribute |
✅ High |
| 2 | self.toolbar attribute |
✅ High |
| 3 | dir() inspection |
Strategies 1 and 2 are strongly recommended for reliable and predictable detection.
Expose your widgets as attributes of your plugin's main class.
⚠️ Critical: Widgets must be declared in__init__()orinitGui()— not inrun()— to be detected at QGIS startup.
class MyPlugin:
def initGui(self):
# ── Expose docks (Strategy 1) ──────────────
self.dock_import = MyImportDock(self.iface.mainWindow())
self.dock_results = MyResultsDock(self.iface.mainWindow())
# List ALL docks in self.docks
self.docks = [
self.dock_import,
self.dock_results,
]
self.iface.addDockWidget(
Qt.RightDockWidgetArea, self.dock_import
)
self.iface.addDockWidget(
Qt.LeftDockWidgetArea, self.dock_results
)
# ── Expose toolbar (Strategy 2) ─────────────
self.toolbar = QToolBar("My Plugin")
self.toolbar.setObjectName("MyPluginToolbar")
self.iface.addToolBar(self.toolbar)
# ── Expose menus (optional) ──────────────────
self.analysis_menu = QMenu("Analysis")
self.analysis_menu.setObjectName("analysis_menu")
self.iface.mainWindow().menuBar().addMenu(
self.analysis_menu
)
⚠️ Important: Every widget must have a unique and stableobjectName. Changing anobjectNamebetween plugin versions will break existing user workspaces.
Create a file named <your_plugin_name>.psp.json at the
root of your plugin folder. This file declares the
recommended workspaces for your plugin's users.
These workspaces are loaded automatically at QGIS startup and appear in the QWorkspace Switcher toolbar.
{
"perspectives": [
{
"name": "Data collection",
"button_style": "text",
"icon": "",
"show_menu_bar": true,
"dropdown_menus": [
{"plugin": "myplugin", "menu": "analysis_menu"}
],
"plugins": {
"myplugin": {
"docks": [
{
"name": "my_import_dock",
"label": "Import",
"visible": true,
"area": "right"
},
{
"name": "my_results_dock",
"label": "Results",
"visible": false,
"area": "left"
}
],
"toolbars": [
{
"name": "MyPluginToolbar",
"label": "My Plugin",
"visible": true,
"area": "top",
"line": 2
}
]
},
"__qgis_native__": {
"docks": [
{
"name": "Layers",
"label": "Layers",
"visible": true,
"area": "left"
}
],
"toolbars": [
{
"name": "mMapNavToolBar",
"label": "Map Navigation",
"visible": true,
"area": "top",
"line": 1
}
]
}
}
},
{
"name": "Results analysis",
"button_style": "text",
"icon": "",
"show_menu_bar": true,
"dropdown_menus": [],
"plugins": {
"myplugin": {
"docks": [
{
"name": "my_import_dock",
"label": "Import",
"visible": false,
"area": "right"
},
{
"name": "my_results_dock",
"label": "Results",
"visible": true,
"area": "right"
}
],
"toolbars": [
{
"name": "MyPluginToolbar",
"label": "My Plugin",
"visible": true,
"area": "top",
"line": 2
}
]
}
}
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✅ | Unique workspace name |
button_style |
string | ✅ | text, icon, icon_text, text_icon |
icon |
string | ❌ | Path to button icon file |
show_menu_bar |
bool | ❌ | Show QGIS menu bar (default: true) |
dropdown_menus |
list | ❌ | Menus to show in button dropdown |
dropdown_menus[].plugin |
string | ✅ | Plugin name |
dropdown_menus[].menu |
string | ✅ | Menu objectName |
plugins |
dict | ✅ | Widget config per plugin |
docks[].name |
string | ✅ | objectName of QDockWidget |
docks[].label |
string | ✅ | Display label |
docks[].visible |
bool | ✅ | Visibility in this workspace |
docks[].area |
string | ✅ | left, right, top, bottom |
toolbars[].name |
string | ✅ | objectName of QToolBar |
toolbars[].label |
string | ✅ | Display label |
toolbars[].visible |
bool | ✅ | Visibility in this workspace |
toolbars[].area |
string | ✅ | top, bottom, left, right |
toolbars[].line |
int | ✅ | Line number in area (1 to 5) |
When multiple .psp.json files are found, workspaces are
merged according to the following priority:
CONFIG_DEFAULT (lowest priority)
↓
plugin_a.psp.json
↓
plugin_b.psp.json
↓
user.psp.json (highest priority)
↓
self._cfg (merged in memory)
- Same name in user and plugin → user version wins
- User deletes a plugin workspace → added to
deleted_perspectivesblacklist → stays deleted across sessions even after plugin updates
In your Python code (initGui):
✅ self.docks = [...] declared
✅ self.toolbar with unique stable objectName
✅ QMenu attributes with unique stable objectName
✅ All widgets declared in initGui(), NOT in run()
In your plugin folder:
✅ <plugin_name>.psp.json created at root folder
✅ objectNames match between code and JSON
✅ Tested in QGIS with QWorkspace Switcher installed
✅ Workspaces documented in your plugin README
User workspaces are stored in:
<qworkspace_switcher_dir>/perspectives/user.psp.json
This file can be shared between team members to standardize working environments across a project. The file includes:
perspectives— list of user workspacesdeleted_perspectives— blacklist of deleted plugin workspaces
{
"perspectives": [
{
"name": "QGIS",
"button_style": "text",
"show_menu_bar": true,
"plugins": { ... }
}
],
"deleted_perspectives": ["Modeling"]
}QWorkspace Switcher follows a layered architecture:
┌──────────────────────────────────────────┐
│ Presentation Layer │
│ MainWindow (Qt Designer) │
│ PerspectiveManager (toolbar) │
└──────────────┬───────────────────────────┘
│
┌──────────────▼───────────────────────────┐
│ Business Layer │
│ PerspectiveEngine │
└───┬──────────────┬──────────────┬────────┘
│ │ │
┌───▼───────┐ ┌────▼──────┐ ┌────▼──────────┐
│PluginDisc.│ │ ConfigIO │ │ Applicators │
│ │ │ │ │ DockApplicator│
│ Dynamic │ │ Single │ │ToolbarApplica.│
│ discovery │ │ source of │ │ StateCapture │
│ of widgets│ │ truth │ │ │
└───────────┘ └────┬──────┘ └───────────────┘
│
┌──────▼──────┐
│user.psp.json│
│*.psp.json │
└─────────────┘
Key design decisions:
self._cfgis the single source of truth — all operations go through memory, no file reads during session- Plugin scan order: third-party plugins first, then native QGIS — prevents widget duplication in registry
QFileSystemWatcherdetects external file changes with_writingflag +QTimerto prevent infinite loops
BSD-2-Clause — see LICENSE file
Adnan Benaboud CNR — Compagnie Nationale du Rhône GitHub
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -m "feat: add my feature" - Push to the branch:
git push origin feature/my-feature - Open a Pull Request
Please use the GitHub issue tracker to report bugs or request features.
- QGIST Workbench — Similar concept without declarative plugin integration system
- QGIS Profiles — Native QGIS user profiles (requires restart to switch)