Moneydance Python Developer Resources

Python scripts and extensions

Moneydance uses Jython 2.7, allowing Python code to run on the Java Virtual Machine (JVM). Python extensions execute inside a Jython interpreter, which typically adds around 200–300 MB of additional memory overhead (over pure java/kotlin extensions)

It’s initially quicker to write Python over Java/Kotlin, and needs no compile, so you can very easily test single / small scripts using Moneydance’s ‘Developer Console’

Kotlin (or Java) is generally recommended for new extensions. Python is ideal for ad-hoc scripting, rapid development, and automation.

Python scripts/extensions:

  • have access to the same published Moneydance API as Java and Kotlin,
  • have access to the standard Java class libraries bundled by Moneydance,
  • allowing scripts / extensions to access the financial data model, integrate with the user interface, respond to application events, add context menu items, and register Home Page widgets.

Refer to the main Moneydance Developer Resources guide for an introduction to the Developer Kit, the Moneydance Core API documentation, focussing on Java/Kotlin extension development.

This guide focuses specifically on developing with Python (Jython). It explains Python-specific extension architecture, packaging, and any differences between Python and Java/Kotlin.


QUICK-START

Common coding guidance: Coding tips · Updating data · Variables reference · GUI / Swing, the EDT & threading· Writing Python and using Java and the Moneydance API

I want to… Go to
Create / run a script in the Developer Console Quick-Start: Sample scripts
Build a packaged, distributable extension (.mxt) Building an Extension

QUICK-START: Sample Python (Jython) scripts

The following sample Python (Jython) scripts can be executed directly from Moneydance’s built-in Python interpreter (Window → Developer Console) and provide useful examples of working with the Moneydance API.

  • python_template.py — Default template script. Demonstrates the basic structure of a Python extension and prints a summary of the first ten transactions in the current dataset.
  • categorize_txns.py — Categorizes transactions based on the contents of the payee field.
  • move_txns.py — Moves transactions from one account to another.
  • set_prices.py — Sets historical and current prices for securities.

Example script that can be run in Developer Console:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import sys; reload(sys); sys.setdefaultencoding('utf8')
global moneydance                           # Entry point into the Moneydance API
mdGUI = moneydance.getUI()                  # Entry point into the GUI
book = moneydance.getCurrentAccountBook()   # Entry point into your dataset

import sys, platform
from java.lang import System
from com.infinitekind.moneydance.model import *
if moneydance.getBuild() >= 5100: from com.infinitekind.util import AppDebug

print("")
print("Moneydance version:        MD%s(%s)" %(moneydance.getVersion(), moneydance.getBuild()))
print("The Moneydance controller: %s >> (the global variable 'moneydance' accesses this key object)" %(moneydance))
print("The UI:                    %s" %(mdGUI))
print("The current data set:     '%s'" %(book))

if book is not None:
    tSet = book.getTransactionSet()
    allAccounts = AccountUtil.allMatchesForSearch(book, AcctFilter.ALL_ACCOUNTS_FILTER)
    print("")
    print("Number of transactions in this dataset: %s" %(tSet.getTransactionCount()))
    print("Number of Accounts:                     %s" %(len([a for a in allAccounts if not a.getAccountType().isCategory() and a.getAccountType() != Account.AccountType.ROOT])))
    print("Number of Categories:                   %s" %(len([a for a in allAccounts if a.getAccountType().isCategory()])))
    print("------")
    msgStr = "Hello world... Printing to Moneydance's console from this python script...."
    if moneydance.getBuild() >= 5100:
        AppDebug.ALL.log(msgStr)        # This is how you write to the Moneydance Console (post MD2024)
    else:
        System.err.println(msgStr)      # This is how you write to the Moneydance Console (pre MD2024)

    counter = 0
    for txn in tSet:
        if counter < 10:
            print("transaction: date %s: description: %s for amount %s"
                  % (txn.getDateInt(), txn.getDescription().ljust(35, " "),
                     txn.getAccount().getCurrencyType().formatFancy(txn.getValue(), ".").rjust(20, " ")))
            counter += 1


See Infinite Kind’s Open source repository and reference implementations


Variables reference

The following global variables can be made available to your python session:

Variable Description Available in
moneydance The main Moneydance object (com.moneydance.apps.md.controller.Main) — referred to as context in Java/Kotlin extensions Always
moneydance_ui The Moneydance GUI - i.e. MoneydanceGUI — can be None if the UI hasn’t loaded yet Always
moneydance_data Your dataset reference - i.e. AccountBook Always
moneydance_extension_id Your extension’s self-defined id (string) Extensions only
moneydance_this_fm Proxy allowing getResourceAsStream() to read files bundled in your mxt (as of MD2024.2 build 5142) Extensions only
moneydance_extension_modulemetadata Your extension’s meta_info.dict data (wrapped) Extensions only
moneydance_extension_parameter The parameter passed by an invoke or handle_event trigger (string) Script-method extensions
moneydance_script_fixed_parameter Optional fixed parameter string, set via "script_fixed_parameter" = "xx" in script_info.dict Script-method extensions
moneydance_extension_scriptreference Your extension’s script_info.dict entry for this action (wrapped) Script-method extensions
moneydance_action_context The MDActionContext object — lets your extension act on the selected txns/accounts Script-method extensions: txn_menu / account_menu actions
moneydance_action_event The constructed java.awt.event.ActionEvent object Script-method extensions: txn_menu / account_menu actions
moneydance_homepage_view Send back the new HomePageView instance Script-method extensions: homepageview action

Notes:

  • moneydance, moneydance_ui, and moneydance_data are only valid for the duration of a script’s top-level execution — Moneydance wipes them once the script exits. Any code that runs later (SwingUtilities.invokeLater(), listeners, threads, Swing components) can no longer rely on them. Therefore: capture a reference once (MD_REF = moneydance) and use only MD_REF from then on — including in deferred execution code.
  • If using ExtensionClass(), your initialize() method receives context and extension_wrapper instead of the globals above — save these with self.ext_context = context (the same object as moneydance).
  • To obtain the UI, use MD_REF_UI = MD_REF.getUI() — but .getUI() will try to load the UI if it isn’t loaded yet, so it’s safer to wait until the "md:file:opened" event before doing this.
  • Do not retain moneydance_ui or moneydance_data beyond their immediate use — holding either can keep the dataset in memory even after the file is closed. Instead call MD_REF.getUI() / MD_REF.getCurrentAccountBook() at each point you actually need them.
  • The same principle applies to any other Moneydance object you reference (Account, AccountBook, Txn, CurrencyType, etc.) — while your code holds a reference, that object (and anything it in turn holds, e.g. the AccountBook) stays in memory.
  • Best practice: execute code inside functions and use local-scoped variables, which are released once the function returns. Avoid storing these objects in module-level globals, or in listeners/closures that outlive the function — those keep the reference (and the memory) alive indefinitely.
  • With scriptinfo-based txn_menu/account_menu actions, your extension receives the selected items directly and appears on all context menus. A true ExtensionObject (XO) differs: it receives MDActionContext and must decide which ActionEvent(s) to return as a list — MD only shows the action on the context menu if that list isn’t empty.

Coding tips

  • We recommend that you install a code editor (e.g. PyCharm) or ideally an IDE such as IntelliJ IDEA CE. Without one of these code editing will be difficult (i.e. managing indents). (NOTE: IntelliJ IDEA CE 2021.1 is the last known version to work properly with jython support)
  • Be aware of encoding issues. Moneydance uses UTF-8 as its default, but Python 2.7 uses ASCII:
    • Use this statement at the beginning of your code to force Python 2.7 to default to UTF-8: import sys; reload(sys); sys.setdefaultencoding('utf8')
    • Avoid using str() and use unicode() instead. Alternatively, use %s within text — e.g. "Hello %s" % (name) rather than "Hello " + str(name)
    • The reference __file__ will not exist when running as an ExtensionClass()
  • Try to stay ‘within’ the API. It’s easy to use other classes in Python (unlike Java/Kotlin), but using code outside the API risks backwards compatibility issues.

  • To access data refer: Accessing the data model - entry points: accounts, currencies, transactions, reminders, reports, budgets

An example of how to access all bank accounts using AccountUtil, a custom AcctFilter:

from com.infinitekind.moneydance.model import Account, AcctFilter, AccountUtil
class MyAcctFilter(AcctFilter):
    def matches(self, acct): return acct.getAccountType() == Account.AccountType.BANK
accts = AccountUtil.allMatchesForSearch(moneydance_data, MyAcctFilter())
for a in allAccounts: print(a)

An example of how to access all credit card accounts, securities and currencies using python list comprehension, AccountUtil, and AcctFilter.ALL_ACCOUNTS_FILTER:

from com.infinitekind.moneydance.model import CurrencyType, Account, AcctFilter, AccountUtil
allSecurities = [s for s in moneydance_data.getCurrencies().getAllCurrencies() if s.getCurrencyType() == CurrencyType.Type.SECURITY]
for s in allSecurities: print(s)
allCurrencies = [c for c in moneydance_data.getCurrencies().getAllCurrencies() if c.getCurrencyType() == CurrencyType.Type.CURRENCY]
for c in allCurrencies: print(c)
allAccounts = [a for a in AccountUtil.allMatchesForSearch(moneydance_data, AcctFilter.ALL_ACCOUNTS_FILTER) if a.getAccountType() == Account.AccountType.CREDIT_CARD]
for a in allAccounts: print(a)
  • To write to Moneydance’s Help/Show Console, refer to Logging to the Moneydance Console.
  • There are some prebuilt popups you can use by calling these methods on moneydance.getUI(): askQuestion() askForInput() showInfoMessage()
  • You can interrogate moneydance.getBuild() to check Moneydance build and handle version control / compatibility accordingly

Updating data

Your extension can update data within Moneydance (e.g. add transactions, edit accounts, add currency/security rates/prices). refer Updating data


Writing Python and using Java and the Moneydance API

You write your code in Python (Jython) syntax, but things you will access will be pure Java — the Moneydance API itself is Java/Kotlin, and Jython is built on Java but complies with Python syntax.

  • Jython often coerses parameters and data to Python types, but sometimes it doesn’t. This matters most when calling or receiving a response from the Moneydance API classes.
  • As you would expect, Java primatives (e.g. int) will be returned as Python int (for example), java.lang.String will normally get returned as Python unicode.
  • This matters most around collections and data types: when Moneydance returns data, you might sometimes get a native Python list, other times you might (for example) receive java.util.ArrayList or java.util.HashMap
  • Another area that can cause confusion is with overloaded classes - e.g. DateRange() defines overloaded constructors that accept a) java long and b) java int. When you call DateRange with Python int’s it happens to call the (wrong) Java long constructor. In this example, the fix is to call DateRange(java.lang.Integer(a), java.lang.Integer(b)) as Integer will get passed, and Java will unbox that to a Java int.
  • Iterators are also handled differently in Python. You can often just iterate Moneydance data collections using Python for a in accounts: type syntax. There are Java Iterators for accounts and transactions. Care should be taken to understand the underlying sort order and in which direction you are iterating data (if this matters to your code)
  • In practice these issues rarely matter, since Jython’s bridging makes the day-to-day syntax feel native — for x in someJavaList:, len(someJavaList), someJavaMap[key] all just work. But it’s worth knowing when:
    • You need to construct a new collection to pass into a Moneydance method that expects a specific Java type (e.g. constructing a java.util.ArrayList directly rather than a Python list(), if a method signature is strict about the type).
    • You’re debugging and type(x) shows something like com.infinitekind.moneydance.model.AccountBook or a Java collection class rather than a Python type — that’s expected, not a bug.
    • You want genuinely Python-native behavior (e.g. Python-specific list methods that don’t exist on the Java side) — in that case, wrap the result yourself: list(someJavaList).

GUI / Swing / EDT / Threading

  • The Moneydance GUI is built on Java Swing. You have full access to swing components.
  • Your extension probably needs a GUI, so JFrame(), JDialog and compoents such as JLabel, JPanel etc are examples of classes to use.
  • You should not instantiate your Swing components until after the UI and the Dataset are loaded.
  • Do not update the Look and Feel (LAF) yourself. If you subclass a JComponent, override .updateUI() and call super(YourClass, self).updateUI().

For dialogs, consider extending SecondaryDialog rather than a using raw JDialog — it handles window registration, size/location persistence, and escape-key behaviour for you. See the main Moneydance Developer Resources: Building GUI with Swing page for a full example — the same pattern works from Python.

Threading and the Swing Event Dispatch Thread (EDT):

For large scripts, especially those with Swing GUI components, take care to run all GUI updates on the EDT, and keep ‘heavy’ non-GUI code off the EDT.

  • Developer Console scripts run off the EDT.
  • MXT "type" = "menu" extensions/scripts run on the EDT (i.e. clicked through from a menu).
  • ExtensionClass() runtime extensions start off the EDT (they trigger before the UI is loaded) — unless triggered during an install/reinstall, in which case they start on the EDT.
  • handle_event() and invoke() may be on or off the EDT depending on where they were called from — code needs to test for this.
  • Extensions can also register themselves as listeners to various data model objects (see Listeners); those callbacks can be called from any thread.

You should query if SwingUtilities.isEventDispatchThread():

  • Use SwingUtilities.invokeLater() and SwingUtilities.invokeAndWait() as appropriate.
  • You can also use SwingWorker to run ‘heavy’ non-GUI code off the EDT.

For beginners - or when using small scripts don’t worry too much about the EDT. Otherwise take appropriate EDT/thread management action(s) in your code

Advanced: Java’s Syncronized is not available in Jython - for locking consider lock = threading.Lock() and then with lock: ...


Building an Extension: overview

  • Minimum build: Moneydance version 2021.1 build 3056 for fully functional Python-based extensions:
    • right-click context menu support was enabled from MD2024 (build 5100)
    • ability to register home / summary screen widgets (HomePageView) was enabled from MD2024.3 (build 5201)
  • Python extensions are executed by the Python Interpreter (which has an additional 200-300MB RAM overhead).

  • Each extension gets its own dedicated Python Interpreter instance — and therefore its own global namespace — created once when that extension is loaded. It is never shared with any other extension; scripts within the same extension share that one namespace (which is why one script can see a variable or class another script in the same extension set), but two different extensions each run in complete isolation from one another, with entirely separate interpreters and separate namespaces.

  • Python extensions have some differences in how they are handled because of the way they are implemented in Moneydance:
    • with Python, the extension container is a proxy and forwards the requested function call over to your Python code, whereas with java/kotin your code is directly executed. You have to be aware of these differences and handle them accordingly.
Java/Kotlin extension
Moneydance
    └── Extension container
            └── Java/Kotlin extension class (and its methods)


Python extension
Moneydance
    └── Extension container
            └── Python Interpreter
                    └── Python extension (and its methods)

Quick Start (5 minutes)

  1. Download the Developer Kit and extract it to a folder of your choice. Refer to the Moneydance Developer Resources guide as foundational information — the Java/Kotlin instructions can mostly be converted to work with Jython.

  2. Open a Terminal and change into the extracted folder.

  3. Generate the bundled sample extension mypythonextension:

    ./gradlew clean genkeys mypythonextension
    

    This also creates your own signing key-pair — you only need to run genkeys once, the first time you set up the project.

  4. Locate the generated extension:

    dist/mypythonextension.mxt
    
  5. Install it in Moneydance using Extensions → Manage Extensions → Add from File…, then select mypythonextension.mxt.

That’s it — the sample extension is now installed and ready to use. To build your own extension, package your .py file(s) and script_info.dict into an mxt using the DevKit’s gradlew build script — the mxt contains your *.py file(s) and script_info.dict at root level, plus meta_info.dict in a folder structure at com/moneydance/modules/features/extension_name/ (this layout differs from Java/Kotlin extensions).

If you want the mxt file ‘signed’ by IK, you’ll need to submit it for review/signing — but you can also just run your mxt using your own personal (unverified) signing in the meantime.

For examples of working Python extensions, visit Infinite Kind’s Open source repository and reference implementations.


Two ways to structure a Python extension

Class-based — your whole extension is a single Python class (ExtensionClass()) with lifecycle methods like initialize(), invoke(), handle_event(), and unload() defined on it. Moneydance calls these methods on your instance directly. This is the more natural pattern if you’re used to Java/Kotlin extensions, and it’s what most real extensions use.

Script-based — there’s no class at all. Instead, you write separate standalone .py files — one per action (e.g. menu_script.py, invoke.py, handle_event.py, unload.py) — and wire them together purely through entries in script_info.dict. Moneydance runs the relevant file when the matching action fires. This is simpler to get started with for a single menu-triggered script, but doesn’t give you a persistent object to hold state between calls — you’re responsible for managing that yourself (e.g. via a shared reference like MD_REF).

Separately from how you structure the code, you also choose how it’s loaded — ad-hoc in the Developer Console for quick testing, or packaged as an installable .mxt for anything persistent.

  Ad-hoc (Developer Console) Packaged (.mxt)
Class-based (ExtensionClass()) Run the class directly in the Console (you’ll be prompted to install). This session-only. Useful for quick testing only. Installed via a "type"="initializer" entry in script_info.dict. Persistent across restarts, appears on the Extensions menu, can call registerFeature()/registerHomePageView(), receives unload() on uninstall.
     
Script-based (script_info.dict actions) Not applicable — script-based extensions are defined entirely through script_info.dict entries and can only be installed as an .mxt. However, you can of course text mini code blocks here. Separate .py files per action, wired via "type"="menu"/"method"/"txn_menu"/"account_menu"/"homepageview" entries in script_info.dict. Persistent, appears on the Extensions menu. The Python interpreter instance and its namespace stay alive between runs — you manage your own state/cleanup.

In practice: the packaged .mxt forms (right column) are what you’ll use for anything real. Running a class ad-hoc in the Console is mainly for quick experimentation before packaging it properly.

Re-entrancy: Moneydance guarantees unload() is called on an extension’s existing instance before a new version is ever loaded and init()/initialize() called on it — both on in-place updates (installModule()) and on any duplicate-ID load (addFeatureModule()). Each load also gets a brand-new class instance and, for Python, a brand-new Python Interpreter with a fresh namespace — never a reused one. So a packaged extension never needs its own re-entrancy guard against being re-initialized while still running. The one place this doesn’t apply is the Developer Console: pasting and re-running code in the same session bypasses this lifecycle entirely, so guard against that yourself if needed.

Class-based

There are two ways to run an ExtensionClass(): ad-hoc, by running it directly in the Developer Console, or packaged, by bundling it into an installable .mxt with a "type"="initializer" entry in script_info.dict (see the table above for how each behaves).

from java.lang import System
class ExtensionClass():
  def __init__(self): pass                                      # standard class constructor - optionally set up variables here
  def getName(self): return "Extension Name"
  def initialize(self, extension_context, extension_object):    # called by Moneydance when extension should initialize
      self.moneydanceContext = extension_context
      self.moneydanceExtensionObject = extension_object
      self.moneydanceContext.registerFeature(extension_object, "uri:string:youwanttosend", None, "Your Extension Name")
  def invoke(self, uri):
      System.err.println("Python extension received invoke command: %s" % (uri))
      print("Python extension received invoke command: %s" % (uri))
  def handle_event(self, eventString):                         
      System.err.println("Python extension received handle_event: %s" % (eventString))
      print("Python extension received handle_event: %s" % (eventString))
  def getActionsForContext(self, context):                     # since MD2024(5100) for (optional) right-click context action support
      System.err.println("Python extension received getActionsForContext: %s" % (context))
      print("Python extension received getActionsForContext: %s" % (context))
      return []
  def unload(self):
      System.err.println("Python extension::unload actions here")
      print("Python extension::unload actions here")
  def toString(self): return "Runtime Extension"
  def __repr__(self): return self.toString()

      
moneydance_extension = ExtensionClass()
Method Called when Java/Kotlin equivalent Notes
__init__(self) Standard Python constructor, when the class is instantiated standard constructor Optionally set up variables here
getName(self) Whenever Moneydance needs to display your extension’s name getName() e.g. on the Extensions menu
initialize(self, extension_context, extension_object) Once, called by Moneydance at application startup, if the extension is installed, or upon module install init() Obtain your context reference here; call registerFeature()/registerHomePageView() if needed. Note: the GUI and dataset are not loaded yet at MD startup (unless this is happening during an install/reinstall, in which case the GUI is available)
invoke(self, uri) User clicks your item on the Extensions menu, or something invokes your registered URI via moneydance.showURL() invoke(uri: String) You can also call this yourself internally to trigger your own actions
handle_event(self, eventString) Moneydance fires an application event (md:file:opened, etc.) handleEvent(appEvent: String) See the main Handling events page for the full list
getActionsForContext(self, context) Each time a right-click context menu is being constructed (since MD2024 build 5100) getActionsForContext(context) Must return quickly; return an empty list if nothing applies
unload(self) Extension is being uninstalled, reinstalled, or Moneydance is shutting down unload() / cleanup() Release all references to data objects here
  • Moneydance’s Developer Console specifically looks for the global variable named moneydance_extension bound to an object — i.e. set on the final line of the code sample above. That assignment is the signal telling the Console this script defines an installable extension, which is what triggers the install-confirmation prompt.
  • script_info.dict needs to contain "type"="initializer" and that’s it!
  • Calling registerFeature() inside initialize() (as in the code sample above) puts your extension on the Extensions menu. Clicking that menu item is what triggers your invoke() method to be called.
  • You can also trigger invoke() manually, by calling moneydance.showURL() with the same URI you registered — useful for firing your own extension’s action programmatically rather than waiting on a menu click. See the main Moneydance Developer Resources: Invoking other Moneydance features and extensions using URIs page for example URIs and the full URI Scheme reference.

Optional: adding a HomePageView

It’s possible to add a Summary / Home screen view (dashboard ‘widget’) to your class-based extension, using HomePageView. Alongside your ExtensionClass(), define a second class for the HomePageViewclass MyHomePageView(HomePageView) — containing the following methods: __init__(), getID(), toString(), __str__(), getGUIView(book), setActive(active), refresh(), reset().

Note: getGUIView() must return a valid Swing JComponent — this is what gets displayed, e.g. return JTextField("Hello World").

To register it, call registerHomePageView() at the same time as registerFeature():

self.moneydanceContext.registerHomePageView(extension_object, MyHomePageView())
# use the global variable `extension_object` as the first parameter, NOT your own extension class's reference — this helps Moneydance unload your widget whenever your extension is unloaded.

If your HomePageView#refresh may be called rapidly (e.g. by listeners), avoid rebuilding the view directly inside it — wrap the rebuild in a CollapsibleRefresher to coalesce bursts into one EDT update. See the main Moneydance Developer Resources: Registering a home page view page for a full example.


Script-based

The ‘script-based’ method Python extension - you need the following minimum construct:

  • script-based extensions must be bundled into an installable .mxt with the relevant script_info.dict entries.
  • your main script: decide when to run it (application launch-time using the initializer, or more normally via the extensions menu click action)
  • using: invoke.py, unload.py, handle_event.py, context_actions_txns.py, context_actions_accts.py, initializer.py scripts [all optional]
  • using: homepageview.py script [optional]
  • it is recommended that you use unload.py to clean-up variables (e.g. delete any references to the account book, close listeners) on uninstall or reinstall
  • script_info.dict will contain a mixture of "type"="menu", "type"="method", "type"="initializer", "type"="txn_menu", "type"="account_menu", "type" = "homepageview" entries

The Python interpreter instance is created once, at MD launch, and stays alive in memory for as long as Moneydance is running — it isn’t recreated each time the user clicks your menu item. That means your script’s variables and namespace persist between runs: re-running the script picks up exactly where it left off last time, including any live objects. Your code needs to handle this explicitly (e.g. reset state yourself if a fresh run shouldn’t inherit it). A bare "type"="menu" entry only wires up the menu click itself — invoke, handle_event, and unload only fire if you also declare separate "type"="method" entries for them in script_info.dict.

For a working example/sample(s) of a ‘script-based’ extension, refer to mypythonextension in the DevKit, or extension_tester in the open source respository: https://github.com/TheInfiniteKind/moneydance_open/tree/main/python_scripts/extension_tester

  • If your extension is uninstalled / (re)installed, then unload() method or the unload.py script will be called depending on how you defined it. Please utilise this to ‘clean up’ before your extension is ‘killed’.. E.g. delete all references to data objects

Listeners

Many listeners are available to allow your extension to be notified upon certain events. refer Listeners


MXT file structure

An MXT is a jar-format archive. Alongside your script files and resources, two files at the root of your extension’s package are required:

MXT [ROOT]
├── script_info.dict                            ← metadata file that describes the entry points for your extension
├── yourextn.py                                 ← your main python script file(s)
├── yourextn_files.*                            ← optional - anything else you need (e.g. readme.txt, or icon files)
com/moneydance/modules/features/yourextn/
└── meta_info.dict                              ← metadata file that describes your extension
  • script_info.dict — script metadata (actions, entry points) that Moneydance reads to link extension entry points with script files.
  • meta_info.dict — extension metadata (module ID, name, version, etc.) that Moneydance reads to identify and load the extension.

Key files you need to create

script_info.dict: Locate in the root of your .mxt file

{
  "actions" = (
    {
      "type" = "initializer"                                                # [optional] - used when installing the extension at run time
      "script_file" = "initializer_script.py"                               
    }
    {
      "type" = "menu"                                                       # [optional entry] - can have multiple entries - defines Extensions menu items
      "script_file" = "menu_script.py"                                      # the script to execute
      "name" = "Extension Name"                                             # name of the extensions menu item
      "name.en-GB" = ".. localized name"                                    # [optional] localized name
      "script_fixed_parameter" = "menu_xxx"                                 # [optional] used for information by your scripts
    }
    {
      "type" = "method"                                                     # [optional entry] - can have multiple entries, one for each method
      "method" = "the_method"                                               # specify: invoke, handle_event, unload
      "script_file" = "the_method.py"                                       # the script to execute
      "script_fixed_parameter" = "the_method"                               # [optional] used for information by your scripts
    }
    {
      "type" = "txn_menu"                                                   # [optional entry] - can appear only once
      "name" = "the context menu popup name for selected transactions"      # the name that appears in the context menu
      "name.en-GB" = ".. localized name"                                    # [optional] localized name
      "script_file" = "context_actions_txns.py"                             # the script to execute
      "script_fixed_parameter" = "context_actions_txns"                     # [optional] used for information by your scripts
    }
    {
      "type" = "account_menu"                                               # [optional entry] - can appear only once
      "name" = "the context menu popup name for selected accounts"          # the name that appears in the context menu
      "name.en-GB" = ".. localized name"                                    # [optional] localized name
      "script_file" = "context_actions_accts.py"                            # the script to execute
      "script_fixed_parameter" = "context_actions_accts"                    # [optional] used for information by your scripts
    }
    {
      "type" = "homepageview"                                               # [optional entry] - can appear only once
      "script_file" = "homepageview.py"                                     # the script to execute
      "name" = "HomePageView widget name"                                   # the summary / home screen widget's name
      "name.en-GB" = ".. localized name"                                    # [optional] localized name
      "script_fixed_parameter" = "homepageview"                             # [optional] used for information by your scripts
    }
  )
}

meta_info.dict: Locate in the ./com/moneydance/modules/features/yourextn/ directory of your .mxt file

{
  "id" = "extension_name"                       # all lowercase - your extension's identity to Moneydance
  "extension_type" = "python"                   # always `python`
  "vendor" = "The Infinite Kind"                # this is you
  "module_build" = "1"                          # your build/version
  "minbuild" = "3056"                           # the minimum MD build for this extension
  "maxbuild" = "9999"                           # optional, not normally used. Max version of MD to run extension on.
  "vendor_url" = "https://infinitekind.com"     # your own url
  "module_name" = "Extension Tester"            # user friendly extension name
  "module_name.en-GB" = "localized name..."     # optional, localized module name
  "module_desc" = "extension description"       # long description
  "mac_sandbox_friendly" = "true"               # optional
}

Moneydance events

Python run-time extensions can receive the same Moneydance application events as Java / Kotlin extensions through the handle_event() method.

The complete list of application events, together with their meanings and usage, is maintained on the main Moneydance Developer Resources page.


Appendix

Class reference

Refer to the main developer guide for a full list of class references and links to the API documentation: Class reference and API documentation


Updated by Stuart Beesley July 2026

essential