Use iface to Control the QGIS Interface in PyQGIS

iface is the one name in PyQGIS that exists only inside QGIS Desktop. It is the handle on the running application's interface — the canvas, the layer tree, the menus, the message bar, the dialogs — and it is what every example that starts "open the Python console and type" depends on. Understanding what belongs to iface and what does not is the difference between a script that runs everywhere and one that runs only when a window is open.

This recipe belongs to QGIS Python Console Basics. It covers what iface exposes, how to get it inside a plugin, the difference between iface and QgsProject, and how to write code that works both with and without it.

The interface and the project are different thingsThe iface object owns everything about the running window: the map canvas, the layer tree view, menus, toolbars, the message bar and the dialogs. The project object owns the data: the layers, their styling, the coordinate system and the project settings. A headless script has the project and no interface.Only one of these survives without a windowiface — the windowmapCanvas()layerTreeView()messageBar()addToolBarIcon()activeLayer()None in a standalone scriptQgsProject — the datamapLayers()mapLayersByName()addMapLayer()crs() · setCrs()layerTreeRoot()available everywherewrite against the right-hand column wherever you can — it is the portable half

Prerequisites

  • QGIS 3.34 LTR or newer, running with a GUI for anything on this page.
  • The Python console open (Plugins → Python Console), or a plugin under development.

Where iface comes from

In the Python console, iface is pre-defined and needs no import. In a plugin, it is handed to you:

def classFactory(iface):
    from .my_plugin import MyPlugin
    return MyPlugin(iface)


class MyPlugin:
    def __init__(self, iface):
        self.iface = iface
        self.canvas = iface.mapCanvas()

Breakdown: classFactory is the entry point QGIS calls when loading a plugin, and the iface it passes is a QgisInterface — the same object the console exposes. Storing it on the instance rather than reaching for a global is what keeps a plugin testable, because a test can pass a mock in its place, as covered in mocking the QGIS interface in plugin tests. Outside a plugin and outside the console, there is no iface and no way to make one: it is the running application's own interface object.

Inside a Processing script or an expression function it is sometimes available and sometimes not, so code that might run in either place should check rather than assume:

try:
    from qgis.utils import iface
except ImportError:
    iface = None

if iface is not None:
    iface.messageBar().pushInfo("Ready", "Layers loaded")
else:
    print("Layers loaded")

Breakdown: qgis.utils.iface is the importable form and is None in a headless application rather than absent, so both the ImportError and the None check earn their place across environments. Structuring reporting this way — message bar when there is a window, print otherwise — is what lets one script serve an interactive user and a scheduled job without branching everywhere.

The parts you will actually use

The canvas. iface.mapCanvas() gives the QgsMapCanvas, which is where extents, refreshes and map tools live.

canvas = iface.mapCanvas()
canvas.setExtent(layer.extent())
canvas.refresh()
print(canvas.scale(), canvas.mapUnitsPerPixel())

Breakdown: setExtent changes what the canvas shows but does not redraw on its own — refresh() is what triggers the render, and forgetting it produces the very common "my zoom did nothing" report. scale() is the denominator of the current map scale, which is what scale-dependent logic compares against. Note that the canvas has its own CRS and extent independent of any layer, so zooming to a layer in a different projection requires transforming the extent first.

The active layer. iface.activeLayer() is whatever is highlighted in the layer tree, and setActiveLayer() changes it.

layer = iface.activeLayer()
if layer is None:
    iface.messageBar().pushWarning("Nothing selected", "Select a layer first")

Breakdown: This returns None when nothing is selected or when the selection is a group, so the check is not optional in anything a user will run. "Active layer" is a purely interface concept — a headless script has no such thing, and code that reaches for it is code that will not run in a batch job. Where a script genuinely operates on one nominated layer, taking it by name from the project is the portable alternative.

The message bar. The right place for feedback that is not an error.

from qgis.core import Qgis

iface.messageBar().pushMessage(
    "Export complete", "Wrote 412 features", level=Qgis.MessageLevel.Success, duration=6
)

Breakdown: duration is in seconds, and zero means the message stays until dismissed — reserve that for things the user must acknowledge. The convenience methods pushInfo, pushWarning, pushCritical and pushSuccess cover the common levels with less typing. A message bar notification is far better than a modal dialog for anything the user does not have to answer, and it is covered in more depth in showing messages with QgsMessageBar.

Every add needs its removeAdding a toolbar icon, a plugin menu entry and a dock widget through iface must be matched by the corresponding removal calls when the plugin unloads. Skipping the removals leaves duplicate icons and menu entries after every plugin reload, and eventually a crash.initGui adds; unload removesinitGui()addToolBarIcon(action)addPluginToMenu(name, action)addDockWidget(area, dock)called once when the plugin loadsunload()removeToolBarIcon(action)removePluginMenu(name, action)removeDockWidget(dock)called on reload and on disablean unpaired add is why reloading a plugin duplicates its toolbar icon

from qgis.PyQt.QtWidgets import QAction
from qgis.PyQt.QtGui import QIcon


class MyPlugin:
    def initGui(self):
        self.action = QAction(QIcon(":/plugins/my/icon.png"), "Run export", self.iface.mainWindow())
        self.action.triggered.connect(self.run)
        self.iface.addToolBarIcon(self.action)
        self.iface.addPluginToMenu("&My Plugin", self.action)

    def unload(self):
        self.iface.removePluginMenu("&My Plugin", self.action)
        self.iface.removeToolBarIcon(self.action)

Breakdown: Parenting the action to iface.mainWindow() keeps Qt's ownership sane and stops the action being garbage-collected while the toolbar still references it — a classic cause of a menu entry that does nothing. The menu name must match exactly between addPluginToMenu and removePluginMenu, ampersand included, or the removal silently fails and the entry persists. Keeping the action on self is required so unload can reach it.

Dock widgets and the main window

Anything that should live alongside the canvas rather than in front of it belongs in a dock.

from qgis.PyQt.QtWidgets import QDockWidget, QLabel
from qgis.PyQt.QtCore import Qt

dock = QDockWidget("Export status", iface.mainWindow())
dock.setObjectName("MyPluginDock")
dock.setWidget(QLabel("Idle"))
iface.addDockWidget(Qt.RightDockWidgetArea, dock)

Breakdown: setObjectName is what lets QGIS remember the dock's position and visibility between sessions — a dock without one reappears in the default place every time and is a small, permanent irritation for users. Parenting to iface.mainWindow() again keeps Qt's ownership correct. addDockWidget takes the Qt dock area constant, and QGIS handles tabbing it with any dock already in that area, which is why a plugin should generally not fight for a specific corner.

iface.mainWindow() itself is useful beyond parenting: it is the right parent for any modal dialog, so that the dialog centres on QGIS and blocks the correct window. A dialog created with no parent can appear behind the main window, which reads to the user as the application having frozen.

What to use instead, where you can

Anything that touches data has a non-interface equivalent, and preferring it makes scripts portable.

Interface wayPortable way
iface.activeLayer()QgsProject.instance().mapLayersByName("roads")[0]
iface.addVectorLayer(path, name, "ogr")QgsProject.instance().addMapLayer(QgsVectorLayer(path, name, "ogr"))
iface.mapCanvas().setExtent(...)set the extent on a QgsMapSettings for rendering
iface.messageBar().pushInfo(...)QgsMessageLog.logMessage(...)
iface.zoomToActiveLayer()compute the extent and use it explicitly

The general rule is that iface is for talking to a person and QgsProject is for talking to data. A script that only uses the second runs in the console, in a plugin, in qgis_process and in a cron job without modification, which is the property that makes headless automation possible at all.

QGIS version compatibility

QgisInterface has been remarkably stable across QGIS 3. Methods have been added rather than removed, so code written for 3.4 generally still runs on 3.44. The Qgis.MessageLevel enum moved into the scoped namespace in 3.30 with the old Qgis.Success names retained. iface.addDockWidget and its removal counterpart are unchanged throughout.

Troubleshooting

  • NameError: name 'iface' is not defined. Not in the console — import it from qgis.utils, or accept that there is none.
  • iface is None in a standalone script. Expected. There is no interface without a window.
  • The toolbar icon appears twice after a reload. unload does not remove what initGui added.
  • A menu entry survives unloading. The menu name string does not match between add and remove.
  • activeLayer() returns None unexpectedly. A group is selected, or focus is in a different panel.
  • A canvas change does not appear. refresh() was not called after changing the extent.

Conclusion

Treat iface as the interface layer and nothing more: use it for the canvas, the message bar, menus and dialogs, and use QgsProject for everything about the data. Pair every add with a remove in a plugin, and guard the import wherever the code might run headless. That split is what turns console experiments into scripts that survive being scheduled.

Frequently Asked Questions

Can I create an iface object myself? No. It is provided by the running application. For tests, a mock implementing the methods you use is the standard approach.

How do I open a layer's attribute table from code?iface.showAttributeTable(layer), which returns the dialog so you can position or filter it. It is one of the several dialog helpers on the interface.

Is iface.mapCanvas() the same canvas as in a layout? No. A layout map item renders independently, which is why layout output is unaffected by the canvas extent.

Where do I find the full list of methods?dir(iface) in the console is the fastest route, and help(iface.addDockWidget) gives the signature — the approach described in exploring the PyQGIS API with dir and help.