Settings UI
The Settings UI allows users to configure your plugin through Noctalia’s settings panel. When users navigate to Plugins > Your Plugin > Configure, your Settings component is displayed.
Basic Structure
Section titled “Basic Structure”A Settings UI component is a QML file that must follow this structure:
import QtQuickimport QtQuick.Layoutsimport qs.Commonsimport qs.Widgets
ColumnLayout { id: root
// Plugin API (injected by the settings dialog system) property var pluginApi: null
// Local state for editing property string editValue: pluginApi?.pluginSettings?.myValue || ""
spacing: Style.marginM
// Your settings controls here
// Required: Save function called by the dialog function saveSettings() { pluginApi.pluginSettings.myValue = root.editValue pluginApi.saveSettings() }}Required Elements
Section titled “Required Elements”pluginApi
Section titled “pluginApi”Injected by the settings dialog system. Provides access to plugin settings and the save function.
property var pluginApi: nullsaveSettings() Function
Section titled “saveSettings() Function”Required. This function is called automatically when the user clicks “Save” in the settings dialog.
function saveSettings() { // Update plugin settings pluginApi.pluginSettings.myValue = root.editValue pluginApi.pluginSettings.otherValue = root.otherEdit
// Persist to disk pluginApi.saveSettings()
Logger.i("MyPlugin", "Settings saved")}Manifest Entry
Section titled “Manifest Entry”Register the settings UI in your plugin’s manifest.json:
{ "id": "my-plugin", "name": "My Plugin", "version": "1.0.0", "entryPoints": { "barWidget": "BarWidget.qml", "settings": "Settings.qml" }, "metadata": { "defaultSettings": { "message": "Hello World", "enabled": true, "count": 0 } }}Local State Pattern
Section titled “Local State Pattern”Always use local state variables to track changes before saving. This allows users to cancel without applying changes:
ColumnLayout { id: root
property var pluginApi: null
// Local state - initialized from saved settings property string editMessage: pluginApi?.pluginSettings?.message || pluginApi?.manifest?.metadata?.defaultSettings?.message || ""
property bool editEnabled: pluginApi?.pluginSettings?.enabled ?? pluginApi?.manifest?.metadata?.defaultSettings?.enabled ?? true
property int editCount: pluginApi?.pluginSettings?.count || pluginApi?.manifest?.metadata?.defaultSettings?.count || 0
// Controls update local state, not settings directly NTextInput { text: root.editMessage onTextChanged: root.editMessage = text }
// Save applies local state to settings function saveSettings() { pluginApi.pluginSettings.message = root.editMessage pluginApi.pluginSettings.enabled = root.editEnabled pluginApi.pluginSettings.count = root.editCount pluginApi.saveSettings() }}Available Widgets
Section titled “Available Widgets”Noctalia provides many widgets for building settings interfaces:
Text Input
Section titled “Text Input”NTextInput { Layout.fillWidth: true label: "Display Message" description: "The message shown in the widget" placeholderText: "Enter a message..." text: root.editMessage onTextChanged: root.editMessage = text}Toggle Switch
Section titled “Toggle Switch”NToggle { Layout.fillWidth: true label: "Enable Feature" description: "Turn this feature on or off" checked: root.editEnabled onCheckedChanged: root.editEnabled = checked}Checkbox
Section titled “Checkbox”NCheckbox { text: "Show notifications" checked: root.editNotifications onCheckedChanged: root.editNotifications = checked}Slider
Section titled “Slider”ColumnLayout { Layout.fillWidth: true spacing: Style.marginS
NLabel { label: "Opacity" description: "Widget transparency level" }
NSlider { Layout.fillWidth: true from: 0 to: 100 value: root.editOpacity onValueChanged: root.editOpacity = value }}Spin Box
Section titled “Spin Box”ColumnLayout { Layout.fillWidth: true spacing: Style.marginS
NLabel { label: "Refresh Interval" description: "Update frequency in seconds" }
NSpinBox { from: 1 to: 60 value: root.editInterval onValueChanged: root.editInterval = value }}Combo Box
Section titled “Combo Box”ColumnLayout { Layout.fillWidth: true spacing: Style.marginS
NLabel { label: "Display Mode" description: "How to show the widget" }
NComboBox { Layout.fillWidth: true model: ["Compact", "Normal", "Expanded"] currentIndex: root.editModeIndex onCurrentIndexChanged: root.editModeIndex = currentIndex }}Color Picker
Section titled “Color Picker”ColumnLayout { Layout.fillWidth: true spacing: Style.marginS
NLabel { label: "Accent Color" description: "Custom color for highlights" }
NColorPicker { Layout.preferredWidth: Style.sliderWidth Layout.preferredHeight: Style.baseWidgetSize selectedColor: root.editColor onColorSelected: function(color) { root.editColor = color } }}Label (No Input)
Section titled “Label (No Input)”NLabel { label: "Section Title" description: "Optional description text"}Layout and Styling
Section titled “Layout and Styling”Spacing
Section titled “Spacing”Use consistent spacing throughout your settings:
ColumnLayout { id: root
spacing: Style.marginM // Standard spacing between controls
// Group related settings ColumnLayout { Layout.fillWidth: true spacing: Style.marginS // Tighter spacing within groups
NLabel { label: "Appearance" }
NToggle { // ... }
NColorPicker { // ... } }
// Divider between sections NDivider { Layout.fillWidth: true Layout.topMargin: Style.marginM Layout.bottomMargin: Style.marginM }
// Another section ColumnLayout { Layout.fillWidth: true spacing: Style.marginS
NLabel { label: "Behavior" }
// ... }}Fill Width
Section titled “Fill Width”Most controls should fill the available width:
NTextInput { Layout.fillWidth: true // ...}
NToggle { Layout.fillWidth: true // ...}Complete Example
Section titled “Complete Example”Here’s a complete settings UI example:
import QtQuickimport QtQuick.Layoutsimport qs.Commonsimport qs.Widgets
ColumnLayout { id: root
property var pluginApi: null
// Local state property string editMessage: pluginApi?.pluginSettings?.message || pluginApi?.manifest?.metadata?.defaultSettings?.message || "Hello World"
property color editBgColor: pluginApi?.pluginSettings?.backgroundColor || pluginApi?.manifest?.metadata?.defaultSettings?.backgroundColor || "#A9AEFE"
property bool editShowIcon: pluginApi?.pluginSettings?.showIcon ?? pluginApi?.manifest?.metadata?.defaultSettings?.showIcon ?? true
property int editRefreshRate: pluginApi?.pluginSettings?.refreshRate || pluginApi?.manifest?.metadata?.defaultSettings?.refreshRate || 30
spacing: Style.marginM
Component.onCompleted: { Logger.i("MyPlugin", "Settings UI loaded") }
// Text input NTextInput { Layout.fillWidth: true label: "Display Message" description: "The message shown in the bar widget" placeholderText: "Enter a message..." text: root.editMessage onTextChanged: root.editMessage = text }
// Color picker ColumnLayout { Layout.fillWidth: true spacing: Style.marginS
NLabel { label: "Background Color" description: "Widget background color" }
NColorPicker { Layout.preferredWidth: Style.sliderWidth Layout.preferredHeight: Style.baseWidgetSize selectedColor: root.editBgColor onColorSelected: function(color) { root.editBgColor = color } } }
NDivider { Layout.fillWidth: true Layout.topMargin: Style.marginS Layout.bottomMargin: Style.marginS }
// Toggle NToggle { Layout.fillWidth: true label: "Show Icon" description: "Display an icon next to the message" checked: root.editShowIcon onCheckedChanged: root.editShowIcon = checked }
// Slider with label ColumnLayout { Layout.fillWidth: true spacing: Style.marginS
NLabel { label: "Refresh Rate" description: "Update interval in seconds: " + root.editRefreshRate }
NSlider { Layout.fillWidth: true from: 5 to: 120 stepSize: 5 value: root.editRefreshRate onValueChanged: root.editRefreshRate = value } }
// Preview section NDivider { Layout.fillWidth: true Layout.topMargin: Style.marginS Layout.bottomMargin: Style.marginS }
NLabel { label: "Preview" }
Rectangle { Layout.fillWidth: true Layout.preferredHeight: 50 color: root.editBgColor radius: Style.radiusM
RowLayout { anchors.centerIn: parent spacing: Style.marginS
NIcon { icon: "heart" visible: root.editShowIcon color: Color.mOnPrimary }
NText { text: root.editMessage color: Color.mOnPrimary pointSize: Style.fontSizeM } } }
// Save function - called by the dialog function saveSettings() { if (!pluginApi) { Logger.e("MyPlugin", "Cannot save: pluginApi is null") return }
pluginApi.pluginSettings.message = root.editMessage pluginApi.pluginSettings.backgroundColor = root.editBgColor.toString() pluginApi.pluginSettings.showIcon = root.editShowIcon pluginApi.pluginSettings.refreshRate = root.editRefreshRate
pluginApi.saveSettings()
Logger.i("MyPlugin", "Settings saved successfully") }}Best Practices
Section titled “Best Practices”- Use local state: Don’t modify
pluginSettingsdirectly until save - Provide defaults: Always fall back to
defaultSettingsfrom manifest - Add descriptions: Help users understand each setting
- Group related settings: Use sections with labels and dividers
- Validate input: Check values before saving if needed
- Show previews: When possible, preview changes before saving
- Log actions: Use Logger for debugging
- Handle null: Always check
pluginApibefore accessing
Accessing Settings Elsewhere
Section titled “Accessing Settings Elsewhere”Once saved, settings are available throughout your plugin:
// In BarWidget.qml, Panel.qml, etc.readonly property string message: pluginApi?.pluginSettings?.message || pluginApi?.manifest?.metadata?.defaultSettings?.message || "Default"See Also
Section titled “See Also”- Getting Started - Create your first plugin
- Plugin API - Full API reference
- Manifest Reference - Default settings configuration
- Translations - Translate your settings UI