> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/nvaccess/nvda/llms.txt
> Use this file to discover all available pages before exploring further.

# Global Plugins

> Create system-wide functionality for NVDA

## Overview

Global Plugins allow you to add functionality that works across all applications. Unlike app modules which are specific to one application, global plugins are loaded when NVDA starts and remain active until NVDA exits.

<Note>
  Use global plugins for features that should be available everywhere, such as:

  * New navigation commands
  * System-wide information scripts
  * Custom braille features
  * OCR functionality
  * Cloud synchronization
</Note>

## Basic Global Plugin Structure

Global plugins must:

1. Be placed in the `globalPlugins` directory
2. Have a unique filename that describes their purpose
3. Define a class called `GlobalPlugin` that inherits from `globalPluginHandler.GlobalPlugin`

<CodeGroup>
  ```python Basic Plugin theme={null}
  # timeAnnouncement.py
  # Global Plugin for enhanced time announcements

  import globalPluginHandler
  from scriptHandler import script
  import ui
  import datetime

  class GlobalPlugin(globalPluginHandler.GlobalPlugin):

  	@script(
  		description="Announces the current time with seconds",
  		gesture="kb:NVDA+f12"
  	)
  	def script_announceTimeDetailed(self, gesture):
  		"""Announce time including seconds."""
  		now = datetime.datetime.now()
  		ui.message(now.strftime("%I:%M:%S %p"))
  ```

  ```python globalPluginHandler.py theme={null}
  # From NVDA source
  class GlobalPlugin(baseObject.ScriptableObject):
  	"""Base global plugin.
  	Global plugins facilitate the implementation of new global commands,
  	support for objects which may be found across many applications, etc.
  	"""
  	
  	def terminate(self):
  		"""Terminate this global plugin.
  		This will be called when NVDA is finished with this global plugin.
  		"""
  		pass
  ```
</CodeGroup>

## Lifecycle Methods

Global plugins have two key lifecycle methods:

<ParamField path="__init__" type="method">
  Called when the plugin is loaded (NVDA startup).

  ```python theme={null}
  def __init__(self, *args, **kwargs):
      super().__init__(*args, **kwargs)
      # Initialize your plugin
      self.myData = {}
  ```
</ParamField>

<ParamField path="terminate" type="method">
  Called when the plugin is being unloaded (NVDA exit).

  ```python theme={null}
  def terminate(self):
      # Clean up resources
      super().terminate()
  ```
</ParamField>

## Handling Events

Global plugins receive events from all applications and controls. Event methods are named `event_eventName` and take three arguments:

```python events.py theme={null}
import globalPluginHandler
import ui
import controlTypes

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

	def event_gainFocus(self, obj, nextHandler):
		"""Called when any object gains focus."""
		# Check if this is a specific type of control
		if obj.role == controlTypes.Role.BUTTON:
			# Do custom processing for buttons
			pass
		# Always call nextHandler to propagate the event
		nextHandler()

	def event_nameChange(self, obj, nextHandler):
		"""Called when any object's name changes."""
		if obj.windowClassName == "SpecialControl":
			ui.message(f"Name changed to: {obj.name}")
		nextHandler()

	def event_foreground(self, obj, nextHandler):
		"""Called when the foreground application changes."""
		# Track application switches
		nextHandler()
```

### Event Method Signature

<ParamField path="self" type="GlobalPlugin">
  The plugin instance
</ParamField>

<ParamField path="obj" type="NVDAObject">
  The NVDA Object on which the event occurred
</ParamField>

<ParamField path="nextHandler" type="callable">
  Function to call to propagate the event to the next handler. **Always call this** unless you have a specific reason not to.
</ParamField>

## Creating Scripts

Scripts are commands bound to keyboard shortcuts that users can execute from anywhere:

<CodeGroup>
  ```python Basic Scripts theme={null}
  import globalPluginHandler
  from scriptHandler import script
  import ui
  import api

  class GlobalPlugin(globalPluginHandler.GlobalPlugin):

  	@script(
  		description="Announces current battery status",
  		category="System Information",
  		gesture="kb:NVDA+shift+b"
  	)
  	def script_announceBattery(self, gesture):
  		"""Announce battery status."""
  		# Get battery information
  		ui.message("Battery: 80% remaining")

  	@script(
  		description="Speaks window title and class",
  		gestures=["kb:NVDA+alt+t", "kb:NVDA+shift+t"]
  	)
  	def script_windowInfo(self, gesture):
  		"""Announce window information."""
  		fg = api.getForegroundObject()
  		ui.message(f"{fg.name}, class: {fg.windowClassName}")
  ```

  ```python Advanced Scripts theme={null}
  import globalPluginHandler
  from scriptHandler import script
  import inputCore
  import ui

  class GlobalPlugin(globalPluginHandler.GlobalPlugin):

  	scriptCategory = "My Plugin"  # Default category for all scripts

  	@script(
  		description="Toggle feature on/off",
  		gesture="kb:NVDA+control+f"
  	)
  	def script_toggleFeature(self, gesture):
  		"""Toggle plugin feature."""
  		self.featureEnabled = not getattr(self, 'featureEnabled', False)
  		state = "enabled" if self.featureEnabled else "disabled"
  		ui.message(f"Feature {state}")

  	@script(
  		description="Process clipboard",
  		bypassInputHelp=True,  # Run even in input help mode
  		allowInSleepMode=True  # Run even when sleep mode is active
  	)
  	def script_processClipboard(self, gesture):
  		"""Process clipboard content."""
  		import api
  		clipboard = api.getClipData()
  		if clipboard:
  			ui.message(f"Clipboard: {len(clipboard)} characters")
  ```
</CodeGroup>

### Script Decorator Parameters

<ParamField path="description" type="str" required>
  User-visible description shown in the Input Gestures dialog
</ParamField>

<ParamField path="category" type="str">
  Category for grouping in Input Gestures (uses plugin's `scriptCategory` if not specified)
</ParamField>

<ParamField path="gesture" type="str">
  Single keyboard gesture (e.g., `"kb:NVDA+shift+v"`)
</ParamField>

<ParamField path="gestures" type="list[str]">
  Multiple keyboard gestures
</ParamField>

<ParamField path="canPropagate" type="bool" default="False">
  Whether the script applies to focus ancestor objects
</ParamField>

<ParamField path="bypassInputHelp" type="bool" default="False">
  Run the script even when input help is active
</ParamField>

<ParamField path="allowInSleepMode" type="bool" default="False">
  Allow script execution when sleep mode is active
</ParamField>

<ParamField path="resumeSayAllMode" type="enum">
  The say all mode to resume after executing the script (from `speech.sayAll.CURSOR`)
</ParamField>

<ParamField path="speakOnDemand" type="bool" default="False">
  Produce speech when called while speech mode is "on-demand"
</ParamField>

## Gesture Formats

Gestures identify input that triggers scripts:

<CodeGroup>
  ```python Keyboard Gestures theme={null}
  # Standard keyboard
  "kb:NVDA+shift+v"
  "kb:control+alt+delete"
  "kb:f1"

  # Laptop layout
  "kb(laptop):NVDA+t"
  "kb(laptop):NVDA+shift+upArrow"

  # Named keys
  "kb:escape"
  "kb:enter"
  "kb:space"
  "kb:tab"
  "kb:backspace"
  "kb:delete"
  "kb:home"
  "kb:end"
  "kb:pageUp"
  "kb:pageDown"
  "kb:upArrow"
  "kb:downArrow"
  "kb:leftArrow"
  "kb:rightArrow"
  ```

  ```python Braille Gestures theme={null}
  # Freedom Scientific braille display
  "br(freedomScientific):leftWizWheelUp"
  "br(freedomScientific):rightWizWheelDown"

  # Alva braille display
  "br(alva.BC640):t3"
  "br(alva):etouch1"

  # Generic braille
  "br:space+dot1+dot2"
  ```

  ```python Other Input theme={null}
  # Touch screen
  "ts:2finger_tap"
  "ts:3finger_flickRight"

  # Braille keyboard input
  "bk:dot1+dot2+dot7"
  ```
</CodeGroup>

## Customizing NVDA Objects

Global plugins can customize NVDA Objects system-wide:

```python objectCustomization.py theme={null}
import globalPluginHandler
from NVDAObjects.IAccessible import IAccessible
import controlTypes

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

	def chooseNVDAObjectOverlayClasses(self, obj, clsList):
		"""Choose overlay classes for any NVDA Object."""
		# Add custom behavior to all edit fields
		if obj.role == controlTypes.Role.EDITABLETEXT:
			clsList.insert(0, EnhancedEditField)
		
		# Add custom behavior to specific window classes
		if obj.windowClassName == "CustomWidget":
			clsList.insert(0, CustomWidget)

class EnhancedEditField(IAccessible):
	"""Enhanced edit field with additional features."""
	
	def _get_description(self):
		"""Add helpful description."""
		return super().description or "Edit field"
	
	def event_gainFocus(self):
		"""Custom focus handling."""
		super().event_gainFocus()
		# Additional announcements

class CustomWidget(IAccessible):
	"""Custom widget support."""
	
	def _get_name(self):
		"""Better name extraction."""
		# Custom logic
		return "Custom widget"
```

## Practical Examples

### Example 1: Application Switcher

```python appSwitcher.py theme={null}
# Enhanced application switching announcements

import globalPluginHandler
import ui

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		self.lastApp = None

	def event_foreground(self, obj, nextHandler):
		"""Announce application switches with detail."""
		appModule = obj.appModule
		if appModule and appModule.appName != self.lastApp:
			self.lastApp = appModule.appName
			# Announce with more detail
			ui.message(
				f"Switched to {obj.name}, {appModule.productName}"
			)
		nextHandler()
```

### Example 2: Clipboard Monitor

```python clipboardMonitor.py theme={null}
# Monitor and enhance clipboard operations

import globalPluginHandler
from scriptHandler import script
import api
import ui
import threading
import time

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		self.clipboardHistory = []
		self.monitoringEnabled = False

	@script(
		description="Toggle clipboard monitoring",
		category="Clipboard",
		gesture="kb:NVDA+shift+c"
	)
	def script_toggleMonitoring(self, gesture):
		"""Toggle clipboard monitoring."""
		self.monitoringEnabled = not self.monitoringEnabled
		state = "enabled" if self.monitoringEnabled else "disabled"
		ui.message(f"Clipboard monitoring {state}")

	@script(
		description="Announce clipboard content",
		gesture="kb:NVDA+c"
	)
	def script_announceClipboard(self, gesture):
		"""Announce what's on the clipboard."""
		text = api.getClipData()
		if text:
			ui.message(f"Clipboard: {text}")
		else:
			ui.message("Clipboard is empty")

	def terminate(self):
		"""Clean up."""
		self.monitoringEnabled = False
		super().terminate()
```

### Example 3: OCR Integration

```python ocrPlugin.py theme={null}
# Global OCR functionality

import globalPluginHandler
from scriptHandler import script
import ui
import api

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

	scriptCategory = "OCR"

	@script(
		description="Recognize text in current navigator object",
		gesture="kb:NVDA+r"
	)
	def script_recognizeNavigator(self, gesture):
		"""Perform OCR on navigator object."""
		navigator = api.getNavigatorObject()
		if not navigator:
			ui.message("No navigator object")
			return
		
		try:
			location = navigator.location
			if not location:
				ui.message("Navigator object has no location")
				return
			
			ui.message("Recognizing...")
			# Perform OCR (simplified)
			result = self._performOCR(location)
			ui.message(result)
		except Exception as e:
			ui.message(f"OCR failed: {e}")

	def _performOCR(self, location):
		"""Perform OCR on screen region."""
		# Implementation would use actual OCR library
		return "Sample OCR result"
```

### Example 4: Quick Settings

```python quickSettings.py theme={null}
# Quick access to NVDA settings

import globalPluginHandler
from scriptHandler import script
import config
import ui
import speech

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

	scriptCategory = "Quick Settings"

	@script(
		description="Cycle through speech rates",
		gesture="kb:NVDA+control+r"
	)
	def script_cycleRate(self, gesture):
		"""Cycle through preset speech rates."""
		rates = [25, 50, 75, 100]
		currentRate = config.conf["speech"]["synth"]["rate"]
		
		# Find next rate
		try:
			idx = rates.index(currentRate)
			nextRate = rates[(idx + 1) % len(rates)]
		except ValueError:
			nextRate = rates[0]
		
		config.conf["speech"]["synth"]["rate"] = nextRate
		speech.setSpeechRate(nextRate)
		ui.message(f"Speech rate: {nextRate}")

	@script(
		description="Toggle capital pitch changes",
		gesture="kb:NVDA+control+p"
	)
	def script_toggleCapPitch(self, gesture):
		"""Toggle capital letter pitch changes."""
		current = config.conf["speech"]["capPitchChange"]
		config.conf["speech"]["capPitchChange"] = not current
		state = "enabled" if not current else "disabled"
		ui.message(f"Capital pitch {state}")
```

## Configuration Management

Global plugins can save and load settings:

```python configuration.py theme={null}
import globalPluginHandler
import config
from scriptHandler import script
import ui

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		# Add configuration spec
		config.conf.spec["myPlugin"] = {
			"enabled": "boolean(default=True)",
			"announceLevel": "integer(default=1, min=0, max=3)",
			"soundEnabled": "boolean(default=False)"
		}
		# Load settings
		self.loadConfig()

	def loadConfig(self):
		"""Load plugin configuration."""
		conf = config.conf["myPlugin"]
		self.enabled = conf["enabled"]
		self.announceLevel = conf["announceLevel"]
		self.soundEnabled = conf["soundEnabled"]

	def saveConfig(self):
		"""Save plugin configuration."""
		conf = config.conf["myPlugin"]
		conf["enabled"] = self.enabled
		conf["announceLevel"] = self.announceLevel
		conf["soundEnabled"] = self.soundEnabled

	@script(description="Toggle plugin")
	def script_toggle(self, gesture):
		"""Toggle plugin on/off."""
		self.enabled = not self.enabled
		self.saveConfig()
		ui.message(f"Plugin {'enabled' if self.enabled else 'disabled'}")

	def terminate(self):
		"""Save config on exit."""
		self.saveConfig()
		super().terminate()
```

## Threading and Timers

<Warning>
  Be careful when using threads. NVDA is not fully thread-safe. Use `wx.CallAfter` or `queueHandler.queueFunction` to execute code on the main thread.
</Warning>

```python threading_example.py theme={null}
import globalPluginHandler
import threading
import wx
import queueHandler
import ui

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		# Start background task
		self.thread = threading.Thread(target=self._backgroundTask)
		self.thread.daemon = True
		self.running = True
		self.thread.start()

	def _backgroundTask(self):
		"""Background task running in separate thread."""
		while self.running:
			# Do background work
			threading.Event().wait(5)  # Wait 5 seconds
			# Queue UI update on main thread
			queueHandler.queueFunction(
				queueHandler.eventQueue,
				ui.message,
				"Background task update"
			)

	def terminate(self):
		"""Stop background task."""
		self.running = False
		if self.thread.is_alive():
			self.thread.join(timeout=1.0)
		super().terminate()
```

## Testing

<Steps>
  <Step title="Enable scratchpad">
    Go to NVDA Settings > Advanced and enable the scratchpad directory.
  </Step>

  <Step title="Create plugin">
    Place your global plugin in `%APPDATA%\nvda\scratchpad\globalPlugins\`.
  </Step>

  <Step title="Restart or reload">
    Either restart NVDA or press `NVDA+Control+F3` to reload plugins.
  </Step>

  <Step title="Test functionality">
    Test your scripts and event handlers in various applications.
  </Step>

  <Step title="Check logs">
    View NVDA log (`NVDA+F1`) for any errors or debug messages.
  </Step>
</Steps>

## Best Practices

<Warning>
  **Performance Considerations:**

  * Avoid heavy processing in event handlers
  * Use threading for long-running operations
  * Always call `nextHandler()` unless you have a specific reason not to
  * Clean up resources in `terminate()`
</Warning>

<Note>
  **Code Quality:**

  * Provide clear script descriptions
  * Use appropriate script categories
  * Log debug information appropriately
  * Handle exceptions gracefully
  * Test in different NVDA configurations
  * Consider internationalization from the start
</Note>

## Common Pitfalls

1. **Forgetting to call nextHandler()**
   ```python theme={null}
   # Wrong
   def event_gainFocus(self, obj, nextHandler):
       ui.message(obj.name)
       # Missing nextHandler() - breaks event chain!

   # Correct
   def event_gainFocus(self, obj, nextHandler):
       ui.message(obj.name)
       nextHandler()  # Always call this
   ```

2. **Not cleaning up in terminate()**
   ```python theme={null}
   # Wrong
   class GlobalPlugin(globalPluginHandler.GlobalPlugin):
       def __init__(self):
           self.file = open("data.txt", "w")
       # File never closed!

   # Correct
   class GlobalPlugin(globalPluginHandler.GlobalPlugin):
       def __init__(self):
           self.file = open("data.txt", "w")
       
       def terminate(self):
           self.file.close()
           super().terminate()
   ```

3. **Blocking the main thread**
   ```python theme={null}
   # Wrong - blocks NVDA
   def script_download(self, gesture):
       data = requests.get("http://example.com")  # Blocking!
       ui.message(data)

   # Correct - use threading
   def script_download(self, gesture):
       threading.Thread(target=self._download).start()

   def _download(self):
       data = requests.get("http://example.com")
       queueHandler.queueFunction(
           queueHandler.eventQueue,
           ui.message,
           data
       )
   ```

## Related Resources

* [App Modules →](/development/addons/app-modules)
* [Custom NVDA Objects →](/development/addons/custom-nvda-objects)
* [API Reference →](/development/addons/api-reference)
* [Distribution Guide →](/development/addons/distribution)
