Skip to content

Hooks

Hooks are the integration points between your plugin and AMC. Each QML file in your plugin is classified by its filename -- AMC scans for hook keywords (case-insensitive) to determine where the file should be loaded.

Hook reference

Hook Filename keyword Purpose Singleton?
Data data Shared state and logic Yes
Settings settings Settings panel No
UpperFlyView upperflyview Flight display overlay No
MenuItemButton menuitembutton Side menu button No
MenuItemView menuitemview Full-screen view No
FlyViewTSButton flyviewtsbutton FlyView tool strip button No
MapItemView mapitemview Custom items on the Plan and Fly view maps No

Naming convention

The hook keyword must appear somewhere in the filename. For example, NetFetchUpperFlyView.qml matches the upperflyview hook. The match is case-insensitive.

Availability

The MapItemView hook requires AMC 2.1.0 or later. On earlier versions the file is ignored. All other hooks are available in every AMC 2.x release.


Data

The Data hook registers a QML file as a singleton. It serves as the shared state and logic layer for all other plugin components.

Requirements:

  • Filename must contain data (e.g., MyPluginData.qml).
  • Must start with pragma Singleton.
  • The QML type name (used by other files) must match the filename without the .qml extension.

Example:

pragma Singleton

import QtQuick
import QtCore
import QGroundControl
import QGroundControl.Plugin

Item {
    id: root

    property bool enabled: false
    property string currentData: "No data"

    Settings {
        id: settings
        category: "MyPlugin"
        property alias enabled: root.enabled
    }

    Component.onCompleted: {
        PluginManager.log("MyPlugin: Data initialized")
    }
}

Other plugin files access the singleton by importing Plugin and using the type name directly:

import QGroundControl.Plugin

// Access the singleton
visible: MyPluginData.enabled

See Data Singleton for a deep dive.


Settings

The Settings hook provides a panel that appears on the AMC Settings page under Plugins.

Settings page showing a loaded plugin

Requirements:

  • Filename must contain settings (e.g., MyPluginSettings.qml).

Standard header pattern:

Every settings panel must use the same header bar: a rounded rectangle containing a CustomSwitch, a bold title label, and (optionally) an expand/collapse chevron. This ensures a consistent look across all plugins on the Settings page. The content below the header (the collapsible area) is fully custom per plugin.

See the full implementations in NetFetchSettings.qml and MFDSettings.qml for the complete pattern including the collapsible animation.


UpperFlyView

The UpperFlyView hook places widgets in the upper area of the flight display.

Flight view with the UpperFlyView hook location highlighted

Requirements:

  • Filename must contain upperflyview (e.g., MyPluginUpperFlyView.qml).

Example:

import QtQuick
import QtQuick.Layouts

import QGroundControl.Controls
import QGroundControl.Palette
import QGroundControl.ScreenTools

import QGroundControl.Plugin

Rectangle {
    width: row.width + ScreenTools.defaultFontPixelWidth * 2
    height: row.height + ScreenTools.defaultFontPixelWidth

    color: qgcPal.windowShade
    border.color: qgcPal.colorGreen
    border.width: 1
    radius: ScreenTools.defaultFontPixelWidth / 2

    visible: MyPluginData.enabled

    RowLayout {
        id: row
        anchors.centerIn: parent
        spacing: ScreenTools.defaultFontPixelWidth

        QGCLabel {
            text: MyPluginData.currentData
            font.pointSize: ScreenTools.mediumFontPointSize
            color: qgcPal.text
        }
    }
}

The MenuItemButton hook adds a button in the side menu drawer. It is typically paired with a MenuItemView to provide a full-screen view.

Side menu drawer showing MenuItemButton entries

AMC pairs buttons with views automatically using a naming convention: strip the hook keyword from each filename (case-insensitive) and match on what remains. For example, MyPluginMenuItemButton.qml pairs with MyPluginMenuItemView.qml because both resolve to MyPlugin.

A button without a matching view is standalone — override onClicked for custom behaviour.

Requirements:

  • Filename must contain menuitembutton (e.g., MyPluginMenuItemButton.qml).
  • Use PluginMenuItemButton (from QGroundControl.Widgets) as the root element. It handles the divider, highlight state, and navigation automatically.

Example (paired with a view):

import QGroundControl.Widgets

PluginMenuItemButton {
    text: "My View"
    icon.source: Qt.resolvedUrl("../img/icon.svg")
}

Example (standalone, no view):

import QGroundControl.Widgets

PluginMenuItemButton {
    text: "Help"
    icon.source: Qt.resolvedUrl("../img/help.svg")
    onClicked: {
        drawer.close()
        Qt.openUrlExternally("https://example.com/help")
    }
}

See Menu Integration for the full pattern.


The MenuItemView hook provides a full-screen view that is shown when the corresponding MenuItemButton is pressed.

Requirements:

  • Filename must contain menuitemview (e.g., MyPluginMenuItemView.qml).
  • Must have a matching MenuItemButton file with the same name (e.g., MyPluginMenuItemButton.qml). An orphaned view with no matching button is ignored.

Example:

import QtQuick
import QGroundControl.Controls
import QGroundControl.Palette
import QGroundControl.ScreenTools

Item {
    anchors.fill: parent

    Rectangle {
        anchors.centerIn: parent
        width: 300
        height: 200
        color: qgcPal.windowShade
        radius: ScreenTools.defaultFontPixelWidth

        QGCLabel {
            anchors.centerIn: parent
            text: "This is my custom full-screen view"
            font.pointSize: ScreenTools.largeFontPointSize
        }
    }
}

See Menu Integration for the full pattern.


FlyViewTSButton

The FlyViewTSButton hook adds a circular button to the FlyView tool strip.

Flight view with the FlyViewTSButton hook location highlighted

Requirements:

  • Filename must contain flyviewtsbutton (case-insensitive), e.g., MyPluginFlyViewTSButton.qml.
  • The root item is sized to ScreenTools.defaultButtonSize × ScreenTools.defaultButtonSize by the host — do not set width or height on the root.
  • Declare property string name: qsTr("My Button") on the root item to provide a label. When present and non-empty, the label renders to the right of the button — but only while the user has label visibility toggled on via the tool strip's expand button. If the property is absent or empty, no label is shown regardless of toggle state. Always wrap the value in qsTr().

Button appearance — strongly recommended pattern:

Always set these four properties exactly as shown to match the built-in tool strip buttons:

QGCHoverButton {
    flat:                       false
    radius:                     height / 2
    backgroundColor:            qgcPal.windowShadeDark
    disabledBackgroundColor:    qgcPal.windowShadeDark
    ...
}

Example:

import QtQuick
import QtQuick.Controls
import QGroundControl.Controls
import QGroundControl.Palette
import QGroundControl.ScreenTools

// Filename contains "flyviewtsbutton" — AMC loads this as a FlyViewTSButton hook.
// The host sets width and height to ScreenTools.defaultButtonSize.
QGCHoverButton {
    flat:                       false
    radius:                     height / 2
    backgroundColor:            qgcPal.windowShadeDark
    disabledBackgroundColor:    qgcPal.windowShadeDark
    imageSource:                Qt.resolvedUrl("../img/my-icon.svg")
    property string name:       qsTr("My Button")  // optional label beside the button

    onClicked: myPopup.open()

    MyPopup { id: myPopup }
}

Key points:

  • QGCHoverButton with radius: height / 2 produces the circular style that matches the existing tool strip buttons.
  • Place all custom UI — popups, drawers, panels — as child items or separate QML files. The host does not restrict child geometry.
  • If no flyviewtsbutton plugins are loaded, no extra space is added to the strip.
  • See the FlyViewTool example for a complete minimal plugin.

MapItemView

The MapItemView hook draws custom items on AMC's maps. A matching file is loaded into both the Plan view map and the Fly view map.

Requires AMC 2.1.0+

This hook was added in AMC 2.1.0. Older versions ignore *MapItemView.qml files — the rest of the plugin still loads normally.

Map with custom plugin markers rendered by the MapItemView hook

Requirements:

  • Filename must contain mapitemview (case-insensitive), e.g., MyPluginMapItemView.qml.
  • The root component must be PluginMapItemView, from QGroundControl.PluginInterface.
  • Never set the map property — AMC owns it.

PluginMapItemView is a QtLocation MapItemView with the live map already attached. There is no other AMC API surface: you set model and delegate exactly as you would on a standard MapItemView. AMC injects nothing else, defines no roles, ships no default delegate, and requires no particular model name — the model shape, role names, delegate content, and z ordering are entirely your choice.

The only AMC-imposed contract

Filename keyword + root component PluginMapItemView + never set map. Everything else is plain QtLocation — see the linked Qt docs below rather than expecting AMC-specific behaviour.

Example:

import QtQuick
import QtLocation
import QtPositioning
import QGroundControl.PluginInterface   // PluginMapItemView (a MapItemView with map preset)

// Filename contains "mapitemview" — AMC loads this onto both maps.
// Do NOT set `map`; AMC injects it.
PluginMapItemView {
    model: ListModel { id: markers }

    delegate: MapQuickItem {
        coordinate:    QtPositioning.coordinate(model.lat, model.lon)
        anchorPoint.x: content.width  / 2
        anchorPoint.y: content.height / 2
        // sourceItem must be a QML item (not an image file) — wrap the icon in an Image.
        sourceItem: Image {
            id:         content
            source:     Qt.resolvedUrl(model.icon)
            sourceSize: Qt.size(32, 32)
        }
    }

    Component.onCompleted: {
        // Use a fixed coordinate, not map.center: each map (Fly/Plan) has its own center
        // and they aren't synced at load, so map.center is not a stable anchor here.
        markers.append({ lat: 47.397, lon: 8.546, icon: "../img/pin.svg" })
    }
}

View targeting (optional):

The view renders on both maps by default. The wrapper exposes two booleans, both defaulting to true, to restrict it to one map:

PluginMapItemView {
    showInPlanView: false   // hide on the Plan view map (Fly view only)
    // showInFlyView: false // hide on the Fly view map (Plan view only)
}

These are load-time constants — read once when the file loads. Do not bind them to runtime-changing state.

Z-order (optional):

z defaults to the generic map-items tier — above vehicles and trajectories, below waypoints, the active vehicle, and widgets. Override z on the root (or per-delegate) to move the whole view, e.g. to sit on top of everything.

Key points:

  • The delegate can be any map item. The example uses a MapQuickItem (which anchors arbitrary screen content — icons, custom QML — at a coordinate). To draw a geographic shape that scales with zoom, make the delegate's root a MapPolyline, MapPolygon, or MapCircle instead. See the map items overview.
  • A MapQuickItem's sourceItem must be a QML item, not an image file — to show an icon, use an Image. (Likewise a Loader's source is a QML component URL, so pointing one at a .svg/.png is a syntax error.)
  • The preset map is read-only to you — you must never assign to it, but you may read it. map.center (a coordinate) is the current map position. Note the Fly and Plan views each have their own map and are not centered together at load, so map.center is not a stable anchor in Component.onCompleted — use a fixed coordinate for static items, or bind to map.center / map.onCenterChanged to follow the live center.
  • A ListModel locks each role's type from the first row that sets it and silently drops later rows that assign a different type to that role. Keep every role one consistent type across all appends (e.g. always pass icon as a string, and resolve it with Qt.resolvedUrl(...) in the delegate).
  • It is runtime-dynamic: mutate the ListModel (append/remove/set rows) and the map items update live.
  • Multiple plugins coexist. Each *MapItemView.qml is attached as an independent MapItemView — AMC never merges models. There is no upper bound.
  • See the MapMarkers example for a complete minimal plugin.