Skip to main content
NVDA’s architecture is built around several key design patterns that work together to provide screen reading functionality across different applications and accessibility APIs.

Core Components

NVDA’s architecture consists of several interconnected components:

NVDA Objects

Representations of GUI controls and widgets

Events

System for handling accessibility events

Scripts & Gestures

Input handling and command binding

Plugins

App modules and global plugins

Architecture Layers

1. API Layer

NVDA supports multiple accessibility APIs through specialized classes:
  • IAccessible/MSAA: Windows legacy accessibility API
  • IAccessible2: Extended accessibility interface
  • UI Automation (UIA): Modern Windows accessibility API
  • Java Access Bridge (JAB): Java application support
  • Window: Direct Win32 window support
These API classes are located in source/NVDAObjects/ with subdirectories for each API.

2. Object Layer

All GUI elements are represented as NVDA Objects, which provide:
  • Standardized properties (name, role, value, states)
  • Navigation (parent, children, next, previous)
  • Event handling
  • Text access through TextInfo objects

3. Plugin Layer

Plugins customize NVDA’s behavior through:
  • App Modules: Application-specific functionality
  • Global Plugins: System-wide features
  • Overlay Classes: Custom NVDA Object behaviors

Base Object System

NVDA uses a sophisticated base object system defined in baseObject.py:

AutoPropertyObject

Provides automatic property generation from getter/setter methods:
class MyObject(AutoPropertyObject):
    def _get_name(self):
        return "My Name"
    
    def _set_name(self, value):
        self._name = value
    
    # Properties can be cached
    _cache_expensiveValue = True
    
    def _get_expensiveValue(self):
        # This result will be cached for one core pump cycle
        return self._calculateExpensiveValue()
Setting cachePropertiesByDefault = True on a class caches all properties by default. Individual properties can override this with _cache_propertyName = True/False.

ScriptableObject

Provides script and gesture binding capabilities:
class MyScriptable(ScriptableObject):
    # Define gestures in a dictionary
    __gestures = {
        "kb:NVDA+shift+t": "sayTime",
        "kb:NVDA+shift+d": "sayDate",
    }
    
    def script_sayTime(self, gesture):
        """Reports the current time"""
        import datetime
        ui.message(str(datetime.datetime.now().time()))
    
    def script_sayDate(self, gesture):
        """Reports the current date"""
        import datetime
        ui.message(str(datetime.datetime.now().date()))

Dynamic Class Creation

One of NVDA’s most powerful features is dynamic class creation. When an NVDA Object is created, it:
  1. Determines the API class (IAccessible, UIA, etc.)
  2. Finds overlay classes through findOverlayClasses()
  3. Allows plugins to add classes via chooseNVDAObjectOverlayClasses()
  4. Constructs a dynamic class combining all chosen classes
  5. Initializes overlay classes by calling initOverlayClass() on each
# Example from DynamicNVDAObjectType.__call__
clsList = []
obj.findOverlayClasses(clsList)  # API-level classes
appModule.chooseNVDAObjectOverlayClasses(obj, clsList)  # App module classes

# Construct dynamic class from bases
bases = tuple(optimized_class_list)
name = "Dynamic_" + "_".join([x.__name__ for x in clsList])
newCls = type(name, bases, {"__module__": __name__})

# Mutate object into new class
obj.__class__ = newCls
This pattern allows NVDA to create highly specialized objects that combine generic API support with application-specific behavior without modifying the core codebase.

Event Flow

When an accessibility event occurs:
  1. OS/API fires event (e.g., focus change)
  2. NVDA receives event through API monitoring
  3. Event propagates through handler chain:
    • Global Plugins
    • App Module
    • Tree Interceptor (if applicable)
    • NVDA Object itself
  4. Handler processes or calls nextHandler() to continue
# In a Global Plugin
def event_gainFocus(self, obj, nextHandler):
    # Do custom processing
    log.info(f"Focus changed to: {obj.name}")
    # Propagate to next handler
    nextHandler()

Script Resolution

When a user presses a key:
  1. Input captured by NVDA
  2. Gesture normalized to identifier (e.g., “kb:NVDA+t”)
  3. Script search in priority order:
    • User gesture map
    • Locale gesture map
    • Braille display driver gestures
    • Global Plugins
    • App Module
    • Tree Interceptor
    • NVDA Object with focus
    • Focus ancestors (if script has canPropagate=True)
    • Global Commands
  4. Script executed if found
# Script resolution order from scriptHandler.py
def _yieldObjectsForFindScript(gesture):
    yield gesture.scriptableObject
    yield from globalPluginHandler.runningPlugins
    yield focus.appModule
    yield braille.handler.display
    yield treeInterceptor
    yield focus
    yield from reversed(api.getFocusAncestors())
    yield globalCommands.commands

Text Access

NVDA provides text access through the TextInfo system:
  • NVDAObjectTextInfo: Default implementation for object properties
  • DisplayModelTextInfo: Screen scraping fallback
  • Specialized TextInfo classes: For editable text, documents, etc.
TextInfo objects support:
  • Unit-based movement (character, word, line, paragraph)
  • Text extraction and formatting info
  • Bounding rectangles for screen location
  • Bookmarking and comparison

Memory Management

NVDA uses several techniques for efficient memory management:
  • Weak references: For cached objects like app modules and tree interceptors
  • Garbage collection tracking: Through garbageHandler.TrackedObject
  • Cache invalidation: Properties cached only for one core pump cycle
  • Dynamic class caching: Reuses dynamically created classes
class AutoPropertyObject(garbageHandler.TrackedObject):
    __instances = weakref.WeakKeyDictionary()
    
    @classmethod
    def invalidateCaches(cls):
        """Invalidate all instance caches"""
        for instance in list(cls.__instances):
            instance.invalidateCache()

Threading Model

NVDA uses a main thread for most operations with:
  • Queue handler: For sequencing operations
  • Event queue: For processing events in order
  • Script execution: Synchronous on main thread
  • Speech/braille: Queued for output
Most NVDA operations must run on the main thread. Use queueHandler.queueFunction() to safely queue operations from background threads.

Configuration System

NVDA’s configuration follows a layered approach:
  • Default config: Built-in defaults
  • User config: User’s configuration directory
  • Configuration profiles: Context-specific settings
  • Scratchpad: Developer testing area

Next Steps

NVDA Objects

Deep dive into NVDA Object system

Events

Learn about event handling

Scripts & Gestures

Master input handling

Writing Plugins

Create your first plugin

Build docs developers (and LLMs) love