Developing with Nextcloud - Part 2 - Developing your first app
Published: 2023-07-05 09:50:26

*This is an ongoing series of articles about developing for Nextcloud. You can find part 1 about how to set up your development environment [here](https://dev.to/daphnemuller/developing-with-nextcloud-part-1-setting-up-for-development-677).*

## Who is Nextcloud?

Nextcloud is a team of enthusiastic open source developers who love to create a fully open source content collaboration platform. We love our Nextcloud community and a lot of our drive comes from giving the people what they need, and a lot of the PR's and apps come from our community and they develop brilliant things. 😉

We also make sure that Nextcloud works on any scale of use, from a Raspberry Pi as well as on a Galera Cluster, so everyone can benefit from the project.

## Why develop with Nextcloud?

Whether it's getting a handle on your photos, your budget or your recipe collection, Nexctloud is your one stop shop for degooglifying yourself.  But there are as many needs for applications as there are people in the world. Nextcloud offers you the basics to build your app upon. Authentication, file handling and sharing, access control, mobile and desktop clients - don't worry about that, we got you covered! All you need to do is think of an app that you'd like to have and start coding. Or if you've already found an app that you love but it misses stuff you'd like to see, go ahead - all code is open source and available on GitHub and you can easily open a PR to add your feature.

We develop with PHP and VueJS. So whether you're frontend, backend or fullstack, I'm sure you can find some fun challenges to work on. We offer a large library of ready- to use vue components too, so you can pick and choose.

## Part 2: Developing your first app

We created an easy to follow tutorial for you to develop your first app. In this tutorial you will learn how to develop a simple files plugin. After this tutorial you will know:

- How the skeleton app is structured
- How actions of a files plugin are structured
- How to use the application.php file to define when the files plugin actions have to run

This tutorial will guide you through the steps of adding an extra action to the ⋯ icon next to a directory or file. For example, in the screenshot below, you can see a directory with an extra 'My directory action' label.
**Screenshot of the app:**
![Screenshot of the app. The screenshot shows the files app of Nextcloud with a file action menu that is added by the app.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4j243tjhp40ietyq1uoa.png)

## Tutorial: Developing a simple files plugin
Before you start, do make sure you have a working development environment. If you don't have a development environment yet, you can find a good tutorial about setting up your development environment [here](https://cloud.nextcloud.com/s/iyNGp8ryWxc7Efa?path=%2F1%20Setting%20up%20a%20development%20environment).

### 1\. Download a skeleton app with the name FilesPlugin and add the skeleton app to the apps-extra folder in your development directory

How to do these steps in detail were covered in the previous tutorial about how to set up your development environment.

You have to place the generated app in the directory **nextcloud-docker-dev / workspace / server**

### 2\. Open the directory of your skeleton app.

This is nextcloud-docker-dev / workspace / server / apps-extra / filesplugin.

> **_NOTE:_**

> **About the skeleton app structure**
> The directory you see now is where we will do our app development.
> You will see several directories:
> \- appinfo (Contains app metadata and configuration)  
> \- img (Contains icons and images)  
> \- lib (Contains the PHP class files, which are pieces of code you can reuse to make your code more performant and easier to maintain)  
> \- LICENSES (Contains the open-source licenses)  
> \- src (Contains the source code of your vue.js app, for advanced front-end design)  
> \- templates (Contains the templates, for example, you could add a template for a left-side bar)  
> \- tests (Contains the automated tests to speed up your release cycle)
> You will also see several files, that you will not really need for now:
> \- babel.config.js (Not needed)  
> \- composer.json (Not needed if there is no PHP dependency which is usually the case)  
> \- Makefile (Using this is up to the developer, it's the classic way to build app releases, but not mandatory)  
> \- package.json (Only needed if there are some javascript dependencies, there is none in this app)  
> \- psalm.xml (Not needed)  
> \- README.md (Contains a user manual and is prominently displayed on e.g. GitHub)  
> \- stylelint.config.js (Only needed if stylelint is installed and used in package.json, not needed here)  
> \- webpack.config.js (Only needed if you write sources in /src/ and compile to /js/)
> **Our files plugin will be written in javascript. Javascript files have to be created in a new directory (with the name) named js, which is the next step.**


### 3 Create a new directory named "js" in the filesplugin directory. In the "js" directory, create a new file named "filesplugin.js"

### 4 Open the "js/filesplugin.js" file, insert the following content

**Code to insert in the filesplugin.js file:**

```
OCA.Files.fileActions.registerAction({
    name: 'myDirectoryAction',
    displayName: t('my-app-id', 'My directory action'),
    mime: 'dir',
    permissions: OC.PERMISSION_READ,
    iconClass: 'icon-folder',
    actionHandler: (name, context) => {
        console.debug('---------- directory action triggered', name, context)
        OC.dialogs.info('The directory "' + name + '" has a size of ' + context.fileInfoModel.attributes.size, 'My directory action')
    },
})

OCA.Files.fileActions.registerAction({
    name: 'myFileAction',
    displayName: t('my-app-id', 'My file action'),
    mime: 'file',
    permissions: OC.PERMISSION_READ,
    iconClass: 'icon-filetype-file',
    actionHandler: (name, context) => {
        console.debug('---------- file action triggered', name, context)
        OC.dialogs.info('The file "' + name + '" has a size of ' + context.fileInfoModel.attributes.size, 'My file action')
    },
})

OCA.Files.fileActions.registerAction({
    name: 'myPdfAction',
    displayName: t('my-app-id', 'My PDF action'),
    mime: 'application/pdf',
    permissions: OC.PERMISSION_READ,
    iconClass: 'icon-file',
    actionHandler: (name, context) => {
        console.debug('---------- pdf action triggered', name, context)
        OC.dialogs.info('The PDF file "' + name + '" has a size of ' + context.fileInfoModel.attributes.size, 'My PDF action')
    },
})
```

> **_NOTE:_**

> **About the structure of the filesplugin.js file**
> This is the code that defines the actions of the files plugin. You see that the code is structured in three blocks. Each block defines a file action: 'My directory action', 'My file action' and 'My pdf action'.
> Adding a file action is done by calling the OCA.Files.fileActions.registerAction() function which takes 5 parameters:
> 1\. name (This is how we refer in the rest of the code to this block)  
> 2\. displayName (This is how we refer in the user interface to this block)  
> 3\. mime (This defines that the code should respond if the selected file is a directory, a file, or a pdf. Note that a pdf is also a file so for a pdf both the file block as the pdf block will be triggered.)  
> 4\. permissions (The necessary permissions to show this action)  
> 5\. iconClass (Defines which icon is used, the core icon classes can be found in [here](https://docs.nextcloud.com/server/21/developer_manual/design/icons.html).\
> You can also set a custom class and define it's style in a custom CSS file that you inject alongside the javascript, for example, this is done in these pieces of code [\[1\]](https://github.com/eneiluj/integration_mattermost/blob/75547448803cd5fcce708180ddc606d375bde4dd/src/filesplugin.js#L63%20) [\[2\]](https://github.com/eneiluj/integration_mattermost/blob/75547448803cd5fcce708180ddc606d375bde4dd/lib/AppInfo/Application.php#L87) [\[3\]](https://github.com/eneiluj/integration_mattermost/blob/75547448803cd5fcce708180ddc606d375bde4dd/css/integration_mattermost-files.css#L2)
> console.debug() will print messages in the browser console (works in all browsers: developer tools -> console tab)
> OC.dialogs.info is a function provided by Nextcloud frontend scripts. It displays an information modal with a confirmation button to close it.
> Now that we defined what the plugin has to do, we have to define when the plugin should be loaded, which we do in the "lib/AppInfo/Application.php file. This is the next step.


### 5 **Open the "lib/AppInfo/Application.php" file.**

> **_NOTE:_**

> **About the structure of the** **Application.php file**
> It's important to understand that the lib/AppInfo/Application.php file of every enabled app is loaded every time you load any page in Nextcloud. For example, if you enable the calendar app, this app's Application.php is also loaded every time when you are browsing the Talk app.
> This means that an app can perform some actions even if it's not used explicitly.
> This also means that if one app you install is badly designed, this can affect the performance and security of your entire instance, even when using other apps.
> The Application.php file contains the dynamic declaration of an app. It defines what the app does in Nextcloud in general, on the server side. For example, an app can run some actions when a specific event is triggered.
> The next step is to extend the construct function to define when the filesplugin.js file should be loaded.


### 6 Insert the following code at the end of the construct function (in between line 14 and 15)

```
// this runs every time Nextcloud loads a page if this app is enabled
$container = $this->getContainer();
$eventDispatcher = $container->get(IEventDispatcher::class);
		
		
// load files plugin script when the Files app triggers the LoadAdditionalScriptsEvent event
$eventDispatcher->addListener(LoadAdditionalScriptsEvent::class, function () {
	// this loads the js/filesplugin.js script once the Files app has done loading its scripts
	Util::addscript(self::APP_ID, 'filesplugin', 'files');
	});
```

> **_NOTE:_**

> **About the structure of this piece of code**
> In the first block of code we define the variables that we need ($container and $eventDispatcher).
> In the second block of code we add a 'listener' which will run each time the Files app triggers the LoadAdditionalScriptsEvent event. If the Files app triggers this event, we then state that an additional script has to be loaded, namely the js/filesplugin.js script.



### 7 Import the following classes in Application.php (in line 9, right below the already existing 'use' declarations)

```
use OCP\EventDispatcher\IEventDispatcher; 
use OCA\Files\Event\LoadAdditionalScriptsEvent; 
use OCP\Util;
```

> **_NOTE:_**
> The app is now ready to be used. The last step is to remove the navigation icon in the top bar (currently the cog icon), because for this app we do not need a navigation item.



### 8 Open the "appinfo/info.xml" file

> **_NOTE:_**

> **About the info.xml file**
> The info.xml file contains all the metadata of the app that you filled in in the app skeleton generator. You can always edit the information here.
> Some interesting fields are the <version> field, in which you define the version of your app. You have to bump up this number if you make changes to the database schema. The Nextcloud server core will trigger an app upgrade process when a version number has changed. But this is not needed for this app as we don't use the server database at all.
> Another interesting field is the <dependencies> field, which defines which Nextcloud server versions your app is compatible with. If someone wants to enable your app on a Nextcloud environment that does not fit this version statement, the red warning button "enable untested app" will appear in the app store.
> For the next step, we are going to edit the <navigations> field, which defines the navigation item in the top bar. We are going to remove this, so that the icon in the top blue bar will disappear.


### 9 Remove the <navigation> tag (in line 22 - 25) in the <navigations> field

Have you saved all the files you changed? Always make sure to save all the files you change for the changes to make effect.


### 10 Visit your Nextcloud app settings and enable your files plugin app.

### Done!

You can now check the app is working fine by opening the context menu of some files in the Files app. You should see your freshly added actions.

### Do you want to learn more?

Do you want to develop an app that does something else than displaying information of the file? Or do you want to develop a similar plugin for another app? This is possible. There are existing pieces of code that use a similar structure that you can draw inspiration from.

* <https://github.com/eneiluj/integration_mattermost> 
  * <https://github.com/eneiluj/integration_mattermost/blob/master/src/filesplugin.js>  
    A file plugin which creates and mounts a Vue component to send files in a Mattermost channel
* <https://github.com/nextcloud/approval> 
  * <https://github.com/nextcloud/approval/blob/master/src/filesplugin.js>  
    A file plugin which injects new content in the file details sidebar
* <https://github.com/nextcloud/files_lock> 
  * <https://github.com/nextcloud/files_lock/blob/master/js/files.js>  
    A file plugin which gets and display extra DAV attributes


## 🤔 Do you have questions?

Just post your questions below this post and we will try to see if we can help you further!

## More tutorials

If you would like to go through more tutorials, we have collected them here:

<https://www.nextcloud.com/developer>

Next week we will post the third part of the tutorial for developing your first app with an interface.