ExApp overview

The basic concept of the AppAPI is to provide a way to develop/write an app for Nextcloud in any language, especially the backend part. The frontend part is usually kept the same. You can think of the ExApp as a microservice.

ExApp structure

The typical ExApp folder structure is the following:

ExApp metadata

The <external-app> info.xml tag is required for the ExApp metadata. It should contain the following fields:

<external-app>
    <docker-install>
        <registry>ghcr.io</registry>
        <image>nextcloud/skeleton</image>
        <image-tag>latest</image-tag>
    </docker-install>
</external-app>
  • docker-install: contains the Docker image information for the ExApp.
    • registry: the Docker registry where the image is stored.

    • image: the Docker image name.

    • image-tag: the Docker image tag (version tag).

Backend

The ExApp backend part can be implemented in any language and framework you want. The only requirement here is to follow the microservice architecture and ExApp <-> Nextcloud :ref:`communication flow <ex_app_lifecycle_methods>`.

Each ExApp container has environment variables set by AppAPI; you can find out more about them :ref:`here <ex_app_env_vars>`.

Persistent storage

For each ExApp, AppAPI creates a Docker volume (nc_app_<app_id>_data) that is attached to the ExApp container as persistent storage. It is available inside the container under the /nc_app_<app_id>_data path or via the APP_PERSISTENT_STORAGE environment variable passed by AppAPI.

Frontend

The ExApp frontend part is loaded only for :ref:`TopMenu entry <top_menu_section>`. It is a regular Nextcloud Vue.js application with a small routing adjustment to the paths, as they are being loaded via AppAPI proxy from the ExApp server.

To simplify the usage, we declare a few constants:

export const EX_APP_ID = 'ui_example'
export const EX_APP_MENU_ENTRY_NAME = 'first_menu'
export const APP_API_PROXY_URL_PREFIX = '/apps/app_api/proxy'
export const APP_API_ROUTER_BASE = '/apps/app_api/embedded'

The bootstrap of the Vue app (UI Example bootstrap) is changed as follows:

import Vue from 'vue'
import { translate, translatePlural } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { APP_API_PROXY_URL_PREFIX, EX_APP_ID } from './constants/AppAPI.js'
import { getCSPNonce } from '@nextcloud/auth'

Vue.prototype.t = translate
Vue.prototype.n = translatePlural
Vue.prototype.OC = window.OC
Vue.prototype.OCA = window.OCA

__webpack_public_path__ = generateUrl(`${APP_API_PROXY_URL_PREFIX}/${EX_APP_ID}/js/`) // eslint-disable-line
__webpack_nonce__ = getCSPNonce() // eslint-disable-line

Frontend routing

The frontend routing base URL is also adjusted to be loaded via AppAPI proxy. For example, the vuex router has the following base URL configuration:

...
const router = new VueRouter({
    mode: 'history',
    base: generateUrl(`${APP_API_ROUTER_BASE}/${EX_APP_ID}/${EX_APP_MENU_ENTRY_NAME}`, ''), // setting base to AppAPI embedded URL
    linkActiveClass: 'active',
...

The same applies to the frontend API requests to the ExApp backend API:

...
axios.get(generateUrl(`${APP_API_PROXY_URL_PREFIX}/${EX_APP_ID}/some_api_endpoint`))
...

L10n translations

To add support of your programming language from translations string extraction using Nextcloud translation tool, you just need to add your file extensions to it in createPotFile and down below adjust the --language and keyword parameters.

Currently only Python is supported as an additional language in translationtool for ExApps. Here is an example using translationtool adjusted for Python:

diff --git a/translations/translationtool/src/translationtool.php b/translations/translationtool/src/translationtool.php
index 42513563..8aa06618 100644
--- a/translations/translationtool/src/translationtool.php
+++ b/translations/translationtool/src/translationtool.php
@@ -67,7 +67,7 @@ public function createPotFile() {
        $this->createFakeFileForVueFiles();
        $this->createFakeFileForLocale();
        $translatableFiles = $this->findTranslatableFiles(
-           ['.php', '.js', '.jsx', '.mjs', '.html', '.ts', '.tsx'],
+           ['.php', '.js', '.jsx', '.mjs', '.html', '.ts', '.tsx', '.py'],
            ['.min.js']
        );

@@ -79,6 +79,8 @@ public function createPotFile() {
            $keywords = '';
            if (substr($entry, -4) === '.php') {
                $keywords = '--keyword=t --keyword=n:1,2';
+           } elseif (substr($entry, -3) === '.py') {
+               $keywords = '--keyword=_ --keyword=_n:1,2';
            } else {
                $keywords = '--keyword=t:2 --keyword=n:2,3';
            }
@@ -86,6 +88,8 @@ public function createPotFile() {
            $language = '--language=';
            if (substr($entry, -4) === '.php') {
                $language .= 'PHP';
+           } elseif (substr($entry, -3) === '.py') {
+               $language .= 'Python';
            } else {
                $language .= 'Javascript';
            }

where we declare the methods used in source code for translating strings.

The ExApp translations are stored in the l10n directory under the ExApp project root directory. On the Nextcloud side, it still has to contain the translation files as with regular Nextcloud apps (.js and .json). The ExApp translation files are copied to the Nextcloud server during installation (removed on uninstall), and can be used to translate ExApp strings on the backend or frontend in the same way as PHP apps.

You might need to convert the translation files to the format that is used in your language. And this can be done with simple bash scripts, as in our example for Python:

Makefile

It is recommended to use the following default set of commands:

For a complete example, you can take a look at our Makefile for the 3rd-party service example. This example also requires the xmlstarlet program to be installed so that the Makefile can automatically detect the ExApp version from the info.xml file.