Plugins

Setting Up Your Development Environment

This guide covers how to set up your development environment for plugin development.

A plugin in neleto is just a normal webserver. This means you can write plugins in any programming language.

The only 2 requirements for your webserver to work as a plugin are:

  1. the plugin.json manifest file. This file defines the name, version, start-command (bin), and metadata of your plugin. Here is a minimal example:
{
    "name": "example-plugin",
    "version": "0.1.0",
    "bin": "bun run main.js"
}
  1. the packaged plugin needs to include everything needed to run it under an x86 ubuntu 24.04 docker container. This includes runtimes for programming languages like javascript, meaning you must bundle them with your plugin. In the case of Bun you can use the --compile when building your plugin to include the bun runtime.

The manifest file plugin.json

To integrate your plugin more with neleto, you can supply more values in the plugin.json. The plugin.json Manifest file needs to conform to this typescript type:

export type ApiEndpoint = {
    /**
     * If this array is not given, all logged in users can access this route
     */
    allowed_roles?: Array<Role>,
    /**
     * This is the route, in a format like this `/foo/:bar/:somepath*`
     */
    route: string,
};

export type PluginHelper = {
    /**
     * The name of the helper
     */
    name: string,
    /**
     * The route, where the helper is located, this route will get the arguments as input and should return the value
     */
    route: string,
    /**
     * The type of this helper
     */
    type: PluginHelperType,
};

export type PluginHelperType = {
    /**
     * the types of the arguments this helper expects, the length defines the amount of arguments
     */
    arguments: Array<VariableType>,
    /**
     * the type of the return value of this helper
     */
    return_value: VariableType,
    /**
     * if this helper is a block helper, like the if helper: `{{#if <arguments> }}<body>{{/if}}`
     */
    block: boolean,
};

export type PluginManifest = {
    /**
     * The name of the plugin
     */
    name: PluginName,
    /**
     * The version of the plugin
     */
    version: string,
    /**
     * The command to start the plugin
     */
    bin: string | null,
    /**
     * Rewriter is a route your plugin handles. this route will be called with POST whenever a
     * page is rendered, with the rendered html in the request body, and the response of this
     * route will be forwarded to either the next plugin, or the render output
     */
    rewriter?: string,
    /**
     * Here you can define routes which function as handlebars helpers when rendering
     */
    helpers?: Array<PluginHelper>,
    /**
     * Here you can define routes which are used as pages when a user visits the website. These
     * routes do not have the prefix in front of their path when called.
     */
    pages?: Array<PluginPage>,
    /**
     * Here you can define routes which are used as authenticated api endpoints. The key is the
     * route, in a format like this `/foo/:bar/:somepath*`. These routes have the prefix
     * `/api/rest/plugins/<your-plugin-name>/api`, when called from the client, as they get
     * proxied through the cms backend.
     */
    api?: Array<ApiEndpoint>,
    /**
     * This is the path to where your plugin defines presets for components, this path needs to be
     * included in your built plugin package Components need to be provided in folders with these
     * files inside: `template.hbs`, `script.js`, `style.css`, `.meta.json` (required),
     * `form.json` (required)
     */
    components?: string,
    /**
     * This is the path to where your plugin defines presets for page-layouts, this path needs to
     * be included in your built plugin package Layouts need to be provided in folders with these
     * files inside: `template.hbs`, `script.js`, `style.css`, `.meta.json` (required)
     */
    layouts?: string,
    /**
     * This route will be used as the entrypoint to the ui of your plugin. These routes have the
     * prefix `/api/rest/plugins/<your-plugin-name>/ui`. The access to these ui routes is only
     * restricted to users who can also manage plugins.
     */
    ui?: string,
};

export type PluginName = string;

export type PluginPage = {
    /**
     * The name of the page
     */
    name: string,
    /**
     * This is the route, in a format like this `/foo/:bar/:somepath*`
     */
    route: string,
};

Routes with this format /foo/:bar/:somepath* follow these rules:

PatternKindDescription
:nameNormalMatches a path piece, excludes /
:name?OptionalMatches an optional path piece, excludes /
/:name?/ /:name?OptionalSegmentMatches an optional path segment, excludes /, prefix or suffix should be /
+ :name+OneOrMoreMatches a path piece, includes /
* :name*ZeroOrMoreMatches an optional path piece, includes /
/*/ /* /:name*/ /:name*ZeroOrMoreSegmentMatches zero or more path segments, prefix or suffix should be /
CaseParameters
:a:ba b
:a:b?a b
:a-:b :a.:b :a~:ba b
:a_a-:b_ba_a b_b
:a\\: :a\\_a
:a\\::b :a\\_:ba b
:a*a
**1
*.**1 *2
:a+a
++1
+.++1 +2
/*/abc/+/def/g*1 +2