Every plugin component receives a pluginApi object that provides access to plugin functionality and Noctalia services.
Plugin API Object
The pluginApi object is injected into all plugin components (BarWidget, DesktopWidget, ControlCenterWidget, LauncherProvider, Panel, Main, Settings):
Item {
// Injected by PluginService
property var pluginApi: null
Component.onCompleted: {
if (pluginApi) {
Logger.i("Plugin", "API available:", pluginApi.pluginId)
}
}
}
Properties
pluginId
- Type:
string - Read-only: Yes
- Description: Unique identifier for this plugin
readonly property string id: pluginApi?.pluginId || ""
Component.onCompleted: {
Logger.i("Plugin", "ID:", pluginApi.pluginId) // "my-plugin"
}
pluginDir
- Type:
string - Read-only: Yes
- Description: Absolute path to plugin directory
readonly property string dir: pluginApi?.pluginDir || ""
// Example: "/home/user/.config/noctalia/plugins/my-plugin"
pluginSettings
- Type:
object - Read-only: No
- Description: User settings object for this plugin
// Read setting
readonly property string message: pluginApi?.pluginSettings?.message || ""
// Write setting
function updateMessage(newMessage) {
pluginApi.pluginSettings.message = newMessage
pluginApi.saveSettings() // Don't forget to save!
}
Warning Always call
saveSettings()after modifyingpluginSettingsto persist changes to disk.
manifest
- Type:
object - Read-only: Yes
- Description: Plugin manifest data
// Access manifest data
readonly property string pluginName: pluginApi?.manifest?.name || ""
readonly property string pluginVersion: pluginApi?.manifest?.version || ""
readonly property var defaultSettings: pluginApi?.manifest?.metadata?.defaultSettings || {}
// Get default value if setting not set
readonly property string message:
pluginApi?.pluginSettings?.message ||
pluginApi?.manifest?.metadata?.defaultSettings?.message ||
"Default"
currentLanguage
- Type:
string - Read-only: Yes
- Description: Current UI language code (e.g.,
"en","es")
readonly property string lang: pluginApi?.currentLanguage || "en"
// Automatically updates when user changes language
pluginTranslations
- Type:
object - Read-only: Yes
- Description: Loaded translations for current language
// Don't access directly - use tr() function instead
// pluginApi.pluginTranslations contains the loaded translation data
mainInstance
- Type:
Item(or null) - Read-only: Yes
- Description: Reference to the instantiated Main.qml component
// Access the main instance from bar widget or panel
if (pluginApi?.mainInstance) {
// Call a function defined in Main.qml
pluginApi.mainInstance.doSomething()
}
Note This property is
nullif your plugin doesn’t have amainentry point in the manifest, or if Main.qml hasn’t been loaded yet.
barWidget
- Type:
Component(or null) - Read-only: Yes
- Description: Reference to the bar widget Component
// Check if plugin has a bar widget
readonly property bool hasBarWidget: pluginApi?.barWidget !== null
Note This property is
nullif your plugin doesn’t have abarWidgetentry point in the manifest.
desktopWidget
- Type:
Component(or null) - Read-only: Yes
- Description: Reference to the desktop widget Component
// Check if plugin has a desktop widget
readonly property bool hasDesktopWidget: pluginApi?.desktopWidget !== null
Note This property is
nullif your plugin doesn’t have adesktopWidgetentry point in the manifest.
controlCenterWidget
- Type:
Component(or null) - Read-only: Yes
- Description: Reference to the control center widget Component
// Check if plugin has a control center widget
readonly property bool hasCCWidget: pluginApi?.controlCenterWidget !== null
Note This property is
nullif your plugin doesn’t have acontrolCenterWidgetentry point in the manifest.
launcherProvider
- Type:
Component(or null) - Read-only: Yes
- Description: Reference to the launcher provider Component
// Check if plugin has a launcher provider
readonly property bool hasProvider: pluginApi?.launcherProvider !== null
Note This property is
nullif your plugin doesn’t have alauncherProviderentry point in the manifest.
panelOpenScreen
- Type:
ShellScreen(or null) - Read-only: Yes
- Availability: Only in Panel.qml
- Description: The screen that the panel was opened on
// In Panel.qml - get the screen this panel is displayed on
var screen = pluginApi?.panelOpenScreen;
if (screen && pluginApi?.manifest) {
BarService.openPluginSettings(screen, pluginApi.manifest);
}
Note This property is only available in Panel components. Use it when you need to reference the screen the panel is currently displayed on.
Functions
saveSettings()
Save plugin settings to disk.
Signature:
function saveSettings(): void
Usage:
function updateSetting(key, value) {
pluginApi.pluginSettings[key] = value
pluginApi.saveSettings()
}
Example:
NButton {
text: "Save"
onClicked: {
pluginApi.pluginSettings.message = messageInput.text
pluginApi.pluginSettings.count = counterValue
pluginApi.saveSettings()
ToastService.showNotice("Settings saved!")
}
}
Tip Settings are saved to
~/.config/noctalia/plugins/<plugin-id>/settings.json
openPanel(screen, buttonItem)
Open this plugin’s panel on the specified screen.
Signature:
function openPanel(screen: ShellScreen, buttonItem?: Item): bool
Parameters:
screen- The ShellScreen to open the panel onbuttonItem- (Optional) The button/widget that triggered the panel. If provided, the panel will position itself near this button.
Returns: true if panel opened successfully, false otherwise
Usage:
// In BarWidget.qml - panel opens near the button
MouseArea {
anchors.fill: parent
onClicked: {
pluginApi.openPanel(root.screen, root)
}
}
Example:
NIconButton {
id: myButton
icon: "external-link"
tooltip: "Open details"
onClicked: {
// Pass 'this' to position panel near the button
if (pluginApi.openPanel(root.screen, this)) {
Logger.i("Plugin", "Panel opened")
} else {
Logger.w("Plugin", "Failed to open panel")
}
}
}
Tip Pass
this(or the widget reference) as the second parameter to have the panel open near the triggering button, similar to how core Noctalia panels work.
Note This only works if your plugin provides a
panelentry point in the manifest.
closePanel(screen)
Close this plugin’s panel.
Signature:
function closePanel(screen: ShellScreen): bool
Parameters:
screen- The ShellScreen to close the panel on
Returns: true if panel closed successfully, false otherwise
Usage:
// In Panel.qml - use pluginApi.panelOpenScreen
NButton {
text: "Close"
onClicked: {
pluginApi.closePanel(pluginApi.panelOpenScreen)
}
}
Warning When calling
closePanel()from within a Panel.qml, you must usepluginApi.panelOpenScreeninstead ofroot.screen. Thescreenproperty is not injected into plugin Panel components, soroot.screenwill be undefined.
togglePanel(screen, buttonItem)
Toggle this plugin’s panel open/closed state.
Signature:
function togglePanel(screen: ShellScreen, buttonItem?: Item): bool
Parameters:
screen- The ShellScreen to toggle the panel onbuttonItem- (Optional) The button/widget that triggered the panel. If provided, the panel will position itself near this button.
Returns: true if operation succeeded, false otherwise
Usage:
// In BarWidget.qml or ControlCenterWidget.qml
NIconButtonHot {
icon: "my-icon"
onClicked: {
pluginApi.togglePanel(root.screen, this)
}
}
Tip
togglePanelis commonly used in bar widgets and control center widgets to open/close the plugin’s panel with a single click. Passthisas the second parameter to have the panel open near the triggering button.
openLauncher(screen)
Open the launcher with this plugin’s command prefix pre-filled. Only available for plugins that provide a launcherProvider entry point.
Signature:
function openLauncher(screen: ShellScreen): void
Parameters:
screen- The ShellScreen to open the launcher on
Usage:
// In Main.qml IPC handler
pluginApi.withCurrentScreen(screen => {
pluginApi.openLauncher(screen);
});
Note The command prefix is determined from the
commandPrefixfield in your manifest’smetadata, falling back to the plugin ID if not specified. For example, a plugin with"commandPrefix": "kaomoji"will open the launcher with>kaomojipre-filled.
closeLauncher(screen)
Close the launcher.
Signature:
function closeLauncher(screen: ShellScreen): void
Parameters:
screen- The ShellScreen to close the launcher on
Usage:
pluginApi.withCurrentScreen(screen => {
pluginApi.closeLauncher(screen);
});
toggleLauncher(screen)
Toggle the launcher open/closed with this plugin’s command prefix. If the launcher is already open with a different command, it switches to this plugin’s prefix instead of closing.
Signature:
function toggleLauncher(screen: ShellScreen): void
Parameters:
screen- The ShellScreen to toggle the launcher on
Usage:
// In Main.qml IPC handler
IpcHandler {
target: "plugin:my-provider"
function toggle() {
pluginApi.withCurrentScreen(screen => {
pluginApi.toggleLauncher(screen);
});
}
}
Behavior:
- If the launcher is closed: opens it with the plugin’s command prefix (e.g.,
>kaomoji) - If the launcher is open with this plugin’s prefix: closes the launcher
- If the launcher is open with a different prefix: switches to this plugin’s prefix
Tip
toggleLauncheris the recommended way to handle IPC toggle commands for launcher provider plugins. It works correctly in both normal and overlay launcher modes.
tr(key, interpolations)
Translate a text key using plugin translations.
Signature:
function tr(key: string, interpolations: object = {}): string
Parameters:
key- Translation key (supports dot notation for nested keys)interpolations- Object with placeholder values (optional)
Returns: Translated string, or ## key ## if translation not found
Usage:
// Simple translation
NText {
text: pluginApi?.tr("panel.title") || "Title"
}
// With interpolations
NText {
text: pluginApi?.tr("welcome.message", { name: userName }) || ""
}
Translation file (i18n/en.json):
{
"panel": {
"title": "My Panel"
},
"welcome": {
"message": "Hello, {name}!"
}
}
Example:
ColumnLayout {
NText {
text: pluginApi?.tr("settings.title") || "Settings"
}
NText {
text: pluginApi?.tr("status.count", { count: itemCount }) || ""
// With count=5: "You have 5 items"
}
}
trp(key, count, defaultSingular, defaultPlural, interpolations)
Translate with plural forms.
Signature:
function trp(
key: string,
count: number,
defaultSingular: string = "",
defaultPlural: string = "",
interpolations: object = {}
): string
Parameters:
key- Base translation keycount- Number to determine singular/pluraldefaultSingular- Fallback singular text (optional)defaultPlural- Fallback plural text (optional)interpolations- Additional placeholder values (optional)
Returns: Translated string with correct plural form
Usage:
NText {
text: pluginApi?.trp(
"items.count",
itemCount,
"1 item",
"{count} items"
) || ""
}
Translation file (i18n/en.json):
{
"items": {
"count": "{count} item",
"count_plural": "{count} items"
}
}
Example:
property int fileCount: 5
NText {
text: pluginApi?.trp(
"files.selected",
fileCount,
"1 file selected",
"{count} files selected"
) || ""
// Output: "5 files selected"
}
hasTranslation(key)
Check if a translation key exists.
Signature:
function hasTranslation(key: string): bool
Parameters:
key- Translation key to check
Returns: true if translation exists, false otherwise
Usage:
if (pluginApi?.hasTranslation("optional.message")) {
text = pluginApi.tr("optional.message")
} else {
text = "Default message"
}
Example:
function getLocalizedHelp() {
if (pluginApi?.hasTranslation("help.advanced")) {
return pluginApi.tr("help.advanced")
}
return pluginApi?.tr("help.basic") || "No help available"
}
withCurrentScreen(callback)
Execute a callback with the current active screen. Useful in IPC handlers where you need a screen reference.
Signature:
function withCurrentScreen(callback: function): void
Parameters:
callback- Function that receives theShellScreenas its argument
Usage:
// In Main.qml IPC handler
function toggle() {
if (pluginApi) {
pluginApi.withCurrentScreen(screen => {
pluginApi.openPanel(screen);
});
}
}
Example:
IpcHandler {
target: "plugin:my-plugin"
function showPanel() {
pluginApi.withCurrentScreen(screen => {
pluginApi.openPanel(screen);
});
}
function closePanel() {
pluginApi.withCurrentScreen(screen => {
pluginApi.closePanel(screen);
});
}
}
Tip This is the preferred way to get a screen reference in IPC handlers, as it automatically selects the appropriate screen based on cursor position.
Accessing Noctalia Services
Plugins have full access to Noctalia services through QML imports.
Common Services
Settings
// Access global Noctalia settings
readonly property bool isDarkMode: Settings.data.ui.darkMode
readonly property string barPosition: Settings.data.bar.position
readonly property string fontMain: Settings.data.ui.fontMain
Internationalization (I18n)
// Access global translations (not plugin translations)
NText {
text: I18n.tr("common.save")
}
// Current language
readonly property string lang: I18n.langCode // "en", "es", etc.
Logger
Component.onCompleted: {
Logger.i("PluginName", "Info message")
Logger.d("PluginName", "Debug:", someValue)
Logger.w("PluginName", "Warning!")
Logger.e("PluginName", "Error occurred:", error)
}
Style
Rectangle {
color: Style.capsuleColor
radius: Style.radiusM
height: Style.barHeight
Layout.margins: Style.marginL
}
NText {
pointSize: Style.fontSizeM
}
Color
NText {
color: Color.mOnSurface // Primary text
color: Color.mOnSurfaceVariant // Secondary text
color: Color.mPrimary // Accent
}
Rectangle {
color: Color.mSurface
color: Color.mSurfaceVariant
color: "transparent"
}
Keybinds
// Check if a key event matches a configured keybind action
Keys.onPressed: function(event) {
if (Keybinds.checkKey(event, "escape", Settings)) {
close();
event.accepted = true;
} else if (Keybinds.checkKey(event, "enter", Settings)) {
submit();
event.accepted = true;
}
}
// Convert a key event to a keybind string (e.g. "Ctrl+A", "Shift+F5")
var keyStr = Keybinds.getKeybindString(event);
// Check for keybind conflicts (returns conflicting action name or null)
var conflict = Keybinds.getKeybindConflict(keyStr, "general.keybinds.keyEscape", Settings.data);
Common action names for checkKey include "up", "down", "left", "right", "enter", and "escape". More keybinds may be available - check Settings.qml under general.keybinds for the full list.
Tip Use
Keybindsinstead of hardcodingQt.Key_*checks. This ensures your plugin respects the user’s custom keybind configuration.
UI Services
ToastService
ToastService.showNotice("Operation completed!")
ToastService.showError("Something went wrong")
TooltipService
MouseArea {
hoverEnabled: true
onEntered: TooltipService.show(parent, "Tooltip text")
onExited: TooltipService.hide()
}
PanelService
// Open other panels
PanelService.openPanel("settings", root.screen)
PanelService.closePanel("launcher", root.screen)
// Check panel state
readonly property bool launcherOpen: PanelService.isPanelOpen("launcher", root.screen)
// Context menus (use these for proper cross-compositor support)
PanelService.showContextMenu(contextMenu, anchorItem, screen)
PanelService.closeContextMenu(screen)
Context Menu Functions
showContextMenu(contextMenu, anchorItem, screen)
Show a context menu anchored to a widget.
onRightClicked: {
PanelService.showContextMenu(contextMenu, root, screen);
}
closeContextMenu(screen)
Close any open context menu on the specified screen.
onTriggered: action => {
contextMenu.close();
PanelService.closeContextMenu(screen);
// Handle action...
}
Tip Always use
PanelService.showContextMenu()instead of directly callingcontextMenu.open(). This ensures proper input handling across all Wayland compositors.
System Services
AudioService
// Volume control
readonly property real volume: AudioService.volume
function setVolume(value) {
AudioService.setVolume(value)
}
// Mute
readonly property bool isMuted: AudioService.muted
function toggleMute() {
AudioService.setMuted(!AudioService.muted)
}
BatteryService
readonly property real batteryPercent: BatteryService.percentage
readonly property bool isCharging: BatteryService.charging
readonly property string batteryIcon: BatteryService.icon
NetworkService
readonly property bool isConnected: NetworkService.connected
readonly property string networkName: NetworkService.ssid
readonly property int signalStrength: NetworkService.signalStrength
Complete Example
Here’s a complete example using most of the Plugin API:
Rectangle {
id: root
property var pluginApi: null
property ShellScreen screen
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
// Get settings with defaults
readonly property string displayText:
pluginApi?.pluginSettings?.text ||
pluginApi?.manifest?.metadata?.defaultSettings?.text ||
"Default"
readonly property int counter:
pluginApi?.pluginSettings?.counter || 0
implicitWidth: row.implicitWidth + Style.marginM * 2
implicitHeight: Style.barHeight
color: Style.capsuleColor
radius: Style.radiusM
RowLayout {
id: row
anchors.centerIn: parent
spacing: Style.marginS
NIcon {
icon: "plugin"
color: Color.mPrimary
}
NText {
text: pluginApi?.tr("widget.title") || "Plugin"
color: Color.mOnSurface
pointSize: Style.fontSizeS
}
NText {
text: root.counter.toString()
color: Color.mPrimary
pointSize: Style.fontSizeS
font.weight: Font.Bold
visible: root.counter > 0
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor
onEntered: {
root.color = Qt.lighter(Style.capsuleColor, 1.1)
// Show tooltip
var tooltip = pluginApi?.trp(
"widget.clicks",
root.counter,
"Click to increment",
"{count} clicks"
) || ""
TooltipService.show(root, tooltip)
}
onExited: {
root.color = Style.capsuleColor
TooltipService.hide()
}
onClicked: function(mouse) {
if (mouse.button === Qt.LeftButton) {
// Increment counter
var newCount = root.counter + 1
pluginApi.pluginSettings.counter = newCount
pluginApi.saveSettings()
Logger.i(pluginApi.pluginId, "Counter incremented to:", newCount)
ToastService.showNotice(
pluginApi?.tr("widget.incremented") || "Incremented!"
)
} else if (mouse.button === Qt.RightButton) {
// Open panel near this widget
if (pluginApi.openPanel(root.screen, root)) {
Logger.i(pluginApi.pluginId, "Panel opened")
}
}
}
}
Component.onCompleted: {
Logger.i("Plugin", "Widget loaded")
Logger.d("Plugin", "ID:", pluginApi?.pluginId)
Logger.d("Plugin", "Version:", pluginApi?.manifest?.version)
Logger.d("Plugin", "Language:", pluginApi?.currentLanguage)
Logger.d("Plugin", "Counter:", root.counter)
}
}
See Also
- Getting Started - Create your first plugin
- Bar Widget - Bar widget development
- Desktop Widget - Desktop widget development
- Control Center Widget - Control center widget development
- Launcher Provider - Launcher provider development
- Panel - Panel development
- Settings UI - Settings UI development
- Translations - i18n guide
- Manifest Reference - Plugin configuration
- Plugin Overview - Plugin system overview