CommCare Extensions

This document describes the mechanisms that can be used to extend CommCare’s functionality. There are a number of legacy mechanisms that are used which are not described in this document. This document will focus on the use of pre-defined extension points to add functionality to CommCare.

Where to put custom code

The custom code for extending CommCare may be part of the main commcare-hq repository or it may have its own repository. In the case where it is in a separate repository the code may be ‘added’ to CommCare by cloning the custom repository into the extensions folder in the root of the CommCare source:

/commcare-hq
  /corehq
  /custom
  ...
  /extensions
    /custom_repo
      /custom_app1/models.py
      /custom_app2/models.py

Extensions Points

The corehq/extensions package provides the utilities to register extension points and their implementations and to retrieve the results from all the registered implementations.

Create an extension point

from corehq import extensions

@extensions.extension_point
def get_things(arg1: int, domain: str, keyword: bool = False) -> List[str]:
    '''Docs for the extension point'''

Registering an extension point implementation

from xyz import get_things

@get_things.extend()
def some_things(arg1, domain, keyword=False):
    return ["thing2", "thing1"]

Extensions may also be limited to specific domains by passing the list of domains as a keyword argument (it must be a keyword argument). This is only supported if the extension point defines a domain argument.

from xyz import get_things

@get_things.extend(domains=["cat", "hat"])
def custom_domain_things(arg1, domain, keyword=False):
    return ["thing3", "thing4"]

Calling an extension point

An extension point is called as a normal function. Results are returned as a list with any None values removed.

from xyz import get_things

results = get_things(10, "seuss", True)

Formatting results

By default the results from calling an extension point are returned as a list where each element is the result from each implementation:

> get_things(10, "seuss", True)
[["thing2", "thing1"], ["thing3", "thing4"]]

Results can also be converted to a flattened list or a single value by passing a ResultFormat enum when defining the extension point.

Flatten Results

@extensions.extension_point(result_format=ResultFormat.FLATTEN)
def get_things(...):
    pass

> get_things(...)
["thing2", "thing1", "thing3", "thing4"]

First Result

This will return the first result that is not None. This will only call the extension point implementations until a value is found.

@extensions.extension_point(result_format=ResultFormat.FIRST)
def get_things(...):
    pass

> get_things(...)
["thing2", "thing1"]

List Extension Points

You can list existing extension points and their implementations by running the following management command:

python manage.py list_extension_points