Skip to main content

Overview

App Modules allow you to improve NVDA’s support for specific applications. When an application is running, NVDA automatically loads the corresponding app module and unloads it when the application closes.
App modules are perfect for adding application-specific functionality, customizing how NVDA presents information from an application, or fixing accessibility issues in specific programs.

Basic App Module Structure

App modules must:
  1. Be placed in the appModules directory
  2. Be named after the application’s executable (e.g., notepad.py for notepad.exe)
  3. Define a class called AppModule that inherits from appModuleHandler.AppModule
# notepad.py
# App Module for Notepad

import appModuleHandler
from scriptHandler import script
import ui
import tones

class AppModule(appModuleHandler.AppModule):

	def event_gainFocus(self, obj, nextHandler):
		"""Called when an object gains focus."""
		tones.beep(550, 50)
		nextHandler()

	@script(
		description="Announces the application version",
		gesture="kb:NVDA+shift+v"
	)
	def script_announceVersion(self, gesture):
		"""Announce the application version."""
		ui.message(f"Application: {self.appName}")

Naming App Modules

Standard Naming

For most applications, name your app module file after the executable:
  • notepad.exenotepad.py
  • explorer.exeexplorer.py
  • firefox.exefirefox.py
File names containing dots (other than .py) are converted to underscores. For example, my.app.exe would use my_app_exe.py.

Custom Mapping

For executables with names that conflict with Python naming rules or when you want to map multiple executables to one app module, use a global plugin to register the mapping:
registrationPlugin.py
import appModuleHandler
import globalPluginHandler

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		# Map "time.exe" to "time_app_mod" module
		appModuleHandler.registerExecutableWithAppModule("time", "time_app_mod")

	def terminate(self, *args, **kwargs):
		super().terminate(*args, **kwargs)
		appModuleHandler.unregisterExecutable("time")

Handling Events

App modules can respond to events that occur in their application. Events are methods named event_eventName.
import appModuleHandler
import ui

class AppModule(appModuleHandler.AppModule):

	def event_gainFocus(self, obj, nextHandler):
		"""Called when an object gains focus."""
		ui.message(f"Focus on {obj.name}")
		nextHandler()  # Always call nextHandler to propagate the event

	def event_focusEntered(self, obj, nextHandler):
		"""Called when focus moves inside a container."""
		if obj.role == controlTypes.Role.DIALOG:
			ui.message(f"Entered dialog: {obj.name}")
		nextHandler()

Common Events

event_gainFocus
method
Object gained keyboard focus
event_loseFocus
method
Object lost keyboard focus
event_focusEntered
method
Focus moved inside a container object (the object is an ancestor of the focus)
event_foreground
method
Object became the new foreground/active top-level object
event_nameChange
method
Object’s name changed
event_valueChange
method
Object’s value changed
event_stateChange
method
Object’s state changed
event_caret
method
Caret (insertion point) moved within the object

Creating Scripts

Scripts are commands that users can execute via keyboard shortcuts. Use the @script decorator to define scripts:
scripts.py
import appModuleHandler
from scriptHandler import script
import ui
import api

class AppModule(appModuleHandler.AppModule):

	@script(
		description="Announces the current object's class name",
		category="My Application",
		gesture="kb:NVDA+shift+c"
	)
	def script_announceClassName(self, gesture):
		"""Announce the window class name."""
		focusObj = api.getFocusObject()
		ui.message(f"Class: {focusObj.windowClassName}")

	@script(
		description="Announces window control ID",
		gestures=["kb:NVDA+shift+i", "kb:NVDA+alt+i"]
	)
	def script_announceControlID(self, gesture):
		"""Announce the control ID."""
		focusObj = api.getFocusObject()
		ui.message(f"Control ID: {focusObj.windowControlID}")

Script Decorator Parameters

description
str
required
User-visible description shown in Input Gestures dialog
category
str
Category to group similar scripts (defaults to “Miscellaneous”)
gesture
str
Single gesture string (e.g., "kb:NVDA+shift+v")
gestures
list[str]
Multiple gesture strings
canPropagate
bool
default:"False"
Whether script applies to focus ancestor objects
bypassInputHelp
bool
default:"False"
Run script even when input help is active
allowInSleepMode
bool
default:"False"
Allow script to run when sleep mode is active

App Module Properties

Essential Properties

properties.py
class AppModule(appModuleHandler.AppModule):
	
	# The process ID (read-only, set in __init__)
	processID: int
	
	# The application name (read-only, set in __init__)
	appName: str
	
	# Handle to the process
	processHandle: int
	
	# Whether NVDA should sleep in this application
	sleepMode: bool = False
	
	# Product name from executable version info
	productName: str
	
	# Product version from executable version info
	productVersion: str
	
	# Whether the process is 64-bit
	is64BitProcess: bool
	
	# Whether this is a Windows Store app
	isWindowsStoreApp: bool

Sleep Mode

Sleep mode disables NVDA in an application, useful for self-voicing applications:
sleepMode.py
import appModuleHandler

class AppModule(appModuleHandler.AppModule):
	"""App module for a self-voicing application."""
	
	sleepMode = True
Users can toggle sleep mode with NVDA+Shift+S.

Customizing NVDA Objects

App modules can customize how NVDA Objects behave for specific controls:
customObjects.py
import appModuleHandler
from NVDAObjects.IAccessible import IAccessible
import controlTypes

class AppModule(appModuleHandler.AppModule):

	def chooseNVDAObjectOverlayClasses(self, obj, clsList):
		"""Choose overlay classes for objects in this application."""
		if obj.windowClassName == "CustomButton":
			# Insert custom class at the beginning
			clsList.insert(0, CustomButton)

class CustomButton(IAccessible):
	"""Custom handling for custom buttons."""
	
	def _get_name(self):
		"""Provide better name for custom buttons."""
		name = super().name
		return f"Custom: {name}"
	
	def _get_role(self):
		"""Override role."""
		return controlTypes.Role.BUTTON

Hosted Apps

Some executables host multiple apps (e.g., javaw.exe, wwahost.exe). For these, you need to:
  1. Name the app module after the hosted app’s internal name
  2. For wwahost.exe, inherit from the wwahost app module
# test.py - for Java app named "test" running in javaw.exe

import appModuleHandler

class AppModule(appModuleHandler.AppModule):
	pass

UIA Handling

Control whether to use UI Automation for specific windows:
uiaControl.py
import appModuleHandler

class AppModule(appModuleHandler.AppModule):

	def isGoodUIAWindow(self, hwnd):
		"""Return True if UIA should be used for this window."""
		# Check window class name and force UIA
		return False

	def isBadUIAWindow(self, hwnd):
		"""Return True if UIA should be avoided for this window."""
		# Detect broken UIA implementations
		return False

	def shouldProcessUIAPropertyChangedEvent(self, sender, propertyId):
		"""Control whether to process UIA property changes."""
		# Filter out noisy events
		return True

Practical Example

Here’s a complete app module for a custom application:
complete_example.py
# myapp.py
# App Module for My Application

import appModuleHandler
from scriptHandler import script
import api
import ui
import controlTypes
from NVDAObjects.IAccessible import IAccessible

class AppModule(appModuleHandler.AppModule):

	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		self.customData = {}

	def event_appModule_gainFocus(self):
		"""Application gained focus."""
		ui.message("My Application")

	def event_gainFocus(self, obj, nextHandler):
		"""Handle focus events."""
		if obj.role == controlTypes.Role.LISTITEM:
			# Custom handling for list items
			self._announceListItem(obj)
		nextHandler()

	def _announceListItem(self, obj):
		"""Custom announcement for list items."""
		position = obj.positionInfo
		if position:
			ui.message(
				f"{obj.name}, item {position['indexInGroup']} of {position['similarItemsInGroup']}"
			)

	@script(
		description="Shows custom information",
		category="My Application",
		gesture="kb:NVDA+shift+i"
	)
	def script_showInfo(self, gesture):
		"""Display custom information dialog."""
		focusObj = api.getFocusObject()
		ui.message(f"Object: {focusObj.name}, Role: {focusObj.role}")

	def chooseNVDAObjectOverlayClasses(self, obj, clsList):
		"""Customize NVDA objects."""
		if obj.windowClassName == "MyCustomControl":
			clsList.insert(0, MyCustomControl)

class MyCustomControl(IAccessible):
	"""Custom NVDA object for MyCustomControl."""
	
	def _get_name(self):
		"""Provide better name."""
		# Custom logic to get name
		return super().name or "Unnamed custom control"
	
	def _get_description(self):
		"""Provide helpful description."""
		return "This is a custom control"

Testing

1

Enable scratchpad

Go to NVDA Settings > Advanced and enable the scratchpad directory.
2

Create app module

Place your app module in %APPDATA%\nvda\scratchpad\appModules\.
3

Launch application

Start the target application.
4

Reload plugins

Press NVDA+Control+F3 to reload without restarting NVDA.
5

Test functionality

Test your events, scripts, and customizations.

Best Practices

  • Always call nextHandler() in event methods to propagate events
  • Don’t perform long-running operations in events
  • Use the @script decorator instead of __gestures dictionary
  • Provide clear descriptions for all scripts
  • Test with different NVDA configurations
  • Use ui.message() for user feedback
  • Use log.debug(), log.info(), log.warning() for debugging
  • Check obj.role and obj.windowClassName for object identification
  • Cache expensive computations in instance variables

Build docs developers (and LLMs) love