Get involved

Do you want to make Zettlr an even better app? That's great! Whether you are simply a user, want to provide a new translation, or get into developing, you've come to the right place.

Here I describe how.


If you are simply a user who wants a good-looking, well-working writing app, just keep your eyes open for any error the app might produce (and I believe there are still some bugs to find!) and, more importantly, tell me how to make the workflow more efficient! I can only judge for my very own workflow, so to make the app better for you as well, I need to know how I could achieve this. Always remember: I cannot build a workflow as-is into the design but have to make concessions to other workflows, but I'll try to make features more accessible or working smoother as long as the trade-offs for the existing workflows and the new suggestion are not too hard.

Please report bugs by opening up issues on the GitHub repository! This way I and other contributors will be able to to quickly respond to the report and directly get to handle the problem.

Translating the App

Translating the app is very easy. All translation files are written in JSON-format, i.e. they are written in the format "placeholder": "This is the translated text".

It's a good start to just download one of the existing language files (I myself currently maintain the German, English and French translations) and just replace the text with a sentence of your own language.

The following things should be respected at all costs:

  • The file must be named in <main-language>_<SUBLANGUAGE>.json, e.g. de_DE.json, en_US.json, etc. If there is no sub-language, just write it by repeating the main language (e.g. it_IT.json)
  • Inside some language strings there are %s-signs. do, under no circumstances, forget them in your translated strings!!! They are used as placeholders for, e.g., the file name. So the app will, for instance, translate the string for the initialization sequence of the dictionaries like this:
    • From: Reading dictionary for %s. This may take a while…
    • To: Reading dictionary for English (UK). This may take a while…
  • The same applies to %PATH% and %NAME%, etc., strings.

If you are happy with your translation, you may want to test it. Simply import it using the File-menu's "Import Translation…"-entry. Point the app to your file and it will read it in. You can then instantly switch to the newly installed language (but don't forget the obligatory restart). It will then use your provided file and you're all set!

If you notice an error or need to change something in your translated file, simply import it once again. Zettlr will automatically overwrite already existing language files and use the new ones on the next restart.

Also, please remember that at least in your language, you should add a specific key for your own language to translate it (from en_US to the more readable English (United States)).

Once you are happy with your translation and want it integrated officially in the app, open a pull-request to the develop-branch!


To start developing, simply fork the repo, work on your features, bug fixes, etc. and then open pull-requests. Please remember to only PR to the develop branch! The master-branch is only pushed to once a new release is being drafted. So if you are developing a new feature and a new version of Zettlr is released, you can simply pull the origin master and merge it into your develop branch to be up to date again and continue writing your feature.

If you are beginning to develop a feature, it also may be wise to announce that using a new issue to just let the rest know that somebody is already doing it to maximise efficiency!

Understanding the application itself

Attention: This part hasn't been updated for a long time and is completely outdated. As the app itself is still subject to major refactions, this part won't be updated until the API is standardised!

Now it is time for some in-depth discussion of the code to help you get kick-started. I won't spend many words on it, just so that you get the first idea of the app.

If you clone the app, you will see two directories, resources and source. Inside the first directory there are only resources used during development that should not be integrated into the published application (mainly to keep it small in size). Among these resources are less-files that are always compiled to css-files prior to a release, application icons for Windows, macOS and Linux platforms and the pandoc-templates.

The pandoc templates are just being copied into the release packages to provide pandoc with default templates during export. If you want to change them, open them in the respective software (you must use Microsoft Word for editing the docx-template because otherwise something might break) and begin changing the styles. Do not add any content to them!

You may also add your own development resources to the directory because the .gitignore is set to ignore everything except the above mentioned directories.

Inside the source-directory there are three subdirectories. The first, common is used for code that is used both by the main process and the renderer process. This includes some helper functions, the languages and a data-file containing some additional semi-dynamical data used by Zettlr.

The second, main contains all classes for the main process, while the third, the renderer-directory, contains all classes (and also assets such as JavaScript- and CSS-files) that are needed for the renderer process.

Main Process

Both processes are basically two gigantic trees of different objects that are constantly held in memory. The base object for the main process is an instance of the class Zettlr. It is created during startup and not deleted until the app shuts down. Please have a look at the rather short main.js-file, which is evaluated on startup.

In the constructor of the Zettlr-class it loads all necessary components: First the configuration because the ZettlrConfig-object loads the config.json to have the configuration ready at hand (or simply creates a default object if there is no config file. If something went utterly wrong, simply delete the config.json—it will be created anew next time the app starts).

ZettlrConfig loads both configuration variables that can be accessed using the get() method as well as some environment variables that can be accessed using getEnv(). The environment variables contain such sensitive things as the language. As Zettlr does not rely on the language format of the app.getLocale() method, this is necessary. I simply don't like simple dashes in language codes. And yes, that is the only reason why this harassment is necessary.

Next, the internationalization is loaded. It is loaded from the respective file by the i18n() method. The JSON file is simply parsed into the global.i18n-object from where the second localization method, trans() draws its strings to translate. The trans() method is heavily influenced by the laravel-framework (PHP-based) using Dot-notation for language strings and tries to either find a fitting string or, if that fails, just returns the given string. This is a good indicator while translating to check which strings still need to be translated.

Afterwards, the ZettlrIPC-class is loaded. This, as well as the renderer pendant is simply a wrapper for the mainIPC and rendererIPC-classes by the electron framework, simplifying the process of sending inter process communcations. The message format is always simple: It only sends "message"s, that contain both a command-attribute and a content-attribute (the latter may be empty if the command does not rely on data).

After the IPC, the core of Zettlr is loaded: The directories. There are two files, zettlr-dir.js and zettlr-file.js that are used for this. A ZettlrDir-object simply assigns itself with a real path on the file system and reads it during initialization. For each directory, a new ZettlrDirectory-object is created that itself reads its directory, while for all files that either end on .txtor .md a new ZettlrFile-object is created that initializes by reading the file's contents and extracting a small snippet. This structure is utterly flexible because it allows for different file types to be read into the tree. Currently, two additional types of files and directories are supported. Each ZettlrDirectory-object may contain ZettlrVirtualDirectory-objects representing virtual directories. They are loaded from .ztr-virtual-directory-files and hold arbitrary selections of files. Also attachments, i.e. non-markdown-files are loaded into the attachments-array of a ZettlrDirectory-object and are displayed on the attachment pane. The attachment-setting in the main preferences dialog controls which files are shown.

As a last step a new ZettlrWindow is created, e.g. the renderer process. It then initializes itself in pretty much the same way the main process does.

One single thing should be mentioned: All IPC-events are handled by a simple switch-command in the handleEvent() method of the main Zettlr object. The object then decides whether it should handle the event itself or delegate the task to one of its children (i.e. the directory tree, the window or the configuration).

Renderer process

The renderer process is structurally very similar to the main process. Once the initial DOM is loaded into the BrowserWindow the $(document).ready() function fires, creating a global renderer-object. As in the main process, this holds all other objects that are created (except CodeMirror and the jQuery-object itself).

The structure of the renderer process consists of an object for each DOM object that should have some functionality attached to it:

  • ZettlrBody: Controls the context menu, the dialogs and the Quicklooks, e.g. everything that affects the complete BrowserWindow (or, in other words, that could appear everywhere or needs a modal).
  • ZettlrCon: Builds a context menu based on the target of the oncontextmenu event and displays it.
  • ZettlrDialog: Displays a full-page modal and a dialog. This class is built to act tightly with the ZettlrBody-class.
  • ZettlrDirectories: Handles everything that happens in the directory pane.
  • ZettlrEditor: Handles the whole main editor pane and everything associated with it.
  • ZettlrNotification: Is used to display small notifications in the top-right edge of the window.
  • ZettlrOverlay: Basically a dialog without the dialog. It is currently only used to disable actions on the BrowserWindow until the dictionaries have been loaded (the typo-js object is pretty nasty)
  • ZettlrPomodoro: Controls the pomodoro timer.
  • ZettlrPopup: This is used to show small popups which let you interact with different elements in the window. For instance, the rename-dialogs are all popups, as are the Pomodor-timer or the formatting-window.
  • ZettlrPreview: Handles everything that happens in the preview pane. It is built to interact with the search field in the toolbar, using the ZettlrRenderer-object as a proxy (yes, the object simply forwards things between the two elements as the search field is a children of the ZettlrToolbar object)
  • ZettlrQuicklook: Simply creates and displays a Quicklook window.
  • ZettlrRenderer: The pendant to the Zettlr object in the renderer process.
  • ZettlrRendererIPC: The pendant to the ZettlrIPC object in the renderer process
  • ZettlrToolbar: Handles all buttons and actions as well as the search bar.