contributions/ServerCodeContributionsGuidelines/PluginCodeContributionsGuidelines.md
Please follow the given guidelines to make sure that your commit sails through the review process without any hiccups.
At this point, we assume that you have Appsmith's server code base setup locally. If not, please check out the guide here.
app/server/appsmith-plugins via the command:mvn archetype:generate \
-DgroupId=com.external.plugins \
-DartifactId=helloWorldPlugin \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
Replace the artifactId with your plugin name. This tutorial will use helloWorldPlugin as a place-holder.
This command will generate a folder called helloWorldPlugin with default source code in the appsmith-plugins directory.
Navigate to your plugin code with your favourite IDE.
Copy the required properties in the plugin's pom.xml file. Example:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>25</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<plugin.id>hello-world-plugin</plugin.id>
<plugin.class>com.external.plugins.HelloWorldPlugin</plugin.class>
<plugin.version>1.0-SNAPSHOT</plugin.version>
<plugin.provider>[email protected]</plugin.provider>
<plugin.dependencies/>
</properties>
Replace the properties plugin.id and plugin.class with your plugin name.
<dependencies>
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-spring</artifactId>
<version>0.7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.appsmith</groupId>
<artifactId>interfaces</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.2.11.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
build command to pom.xml:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<minimizeJar>false</minimizeJar>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Plugin-Id>${plugin.id}</Plugin-Id>
<Plugin-Class>${plugin.class}</Plugin-Class>
<Plugin-Version>${plugin.version}</Plugin-Version>
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
</manifestEntries>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
plugin.properties with the following content:plugin.id=hello-world-plugin
plugin.class=com.external.plugins.HelloWorldPlugin
plugin.version=1.0-SNAPSHOT
[email protected]
plugin.dependencies=
Please remember that the plugin.class and plugin.id MUST be the same as the ones defined in your pom.xml.
Navigate to your plugin's Java source folder src/ and create a new class file HelloWorldPlugin.java. This is the same name as defined in your pom.xml property plugin.class.
Ensure that the class has the following structure:
public class HelloWorldPlugin extends BasePlugin {
public HelloWorldPlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Slf4j
@Extension
public static class HelloWorldPluginExecutor implements PluginExecutor<Object> {
}
}
The BasePlugin & PluginExecutor classes define the basic operations of plugin execution
DatabaseChangelog.java file. Eg: @ChangeSet(order = "076", id = "add-hello-world-plugin", author = "")
public void addHelloWorldPlugin(MongoTemplate mongoTemplate) {
Plugin plugin = new Plugin();
plugin.setName("Hello World Plugin");
plugin.setType(PluginType.DB);
plugin.setPackageName("hello-world-plugin");
plugin.setUiComponent("DbEditorForm");
plugin.setResponseType(Plugin.ResponseType.JSON);
plugin.setIconLocation("https://your-plugin-icon-location.png");
plugin.setDocumentationLink("https://link-to-plugin-documentation.html");
plugin.setDefaultInstall(true);
// Field to distinguish if the plugin is supported in air-gap instance, by default all the plugins will be
// supported. One can opt out by adding this field in DB object. Generally SaaS plugins and DB which can't be
// self-hosted can be a candidate for opting out of air-gap
plugin.setSupportedForAirGap(false);
// Config to set if the plugin has any dependency on cloud-services
plugin.setIsDependentOnCS(true);
try {
mongoTemplate.insert(plugin);
} catch (DuplicateKeyException e) {
log.warn(plugin.getPackageName() + " already present in database.");
}
installPluginToAllWorkspaces(mongoTemplate, plugin.getId());
}
editor.json and form.json in the src/main/resources folder. These JSON files are required to render the UI for your plugin. Samples are given below:# form.json
{
"form": [
{
"sectionName": "Details",
"id": 1,
"children": [
{
"label": "DB Username",
"configProperty": "datasourceConfiguration.authentication.username",
"controlType": "INPUT_TEXT",
"isRequired": true,
"placeholderText": "",
"initialValue": ""
},
{
"label": "DB Password",
"configProperty": "datasourceConfiguration.authentication.password",
"controlType": "INPUT_TEXT",
"dataType": "PASSWORD",
"initialValue": "",
"encrypted": true
}
]
}
]
}
# editor.json
{
"editor": [
{
"sectionName": "",
"id": 1,
"children": [
{
"label": "",
"configProperty": "actionConfiguration.body",
"controlType": "QUERY_DYNAMIC_TEXT"
}
]
}
]
}
cd app/server && ./build.sh && cd scripts && ./start-dev-server.sh
As much as possible, please try to abide by the following code design:
com.external.plugins.DatabaseChangelog.java like
here in DatabaseChangelog.java.form.json and the rendered web page.editor.json and the rendered web page.Maven to manage package dependencies, hence please add all your dependencies in POM file as shown in this
pom.xml file for postgreSQL plugin.DbnamePlugin.java, for example: PostgresPlugin.java.xyz.*.private static identifier
for usage. For example, instead of using "date" string directly, assign it to a private static identifier like private static final String DATE_COLUMN_TYPE_NAME = "date";StaleConnectionException.
This exception is caught by Appsmith's framework and a new connection is established before running the query.
For reference, please check the usage of StaleConnectionException in
PostgresPlugin.java.System.out.println(Thread.currentThread(). getName() + ": <your error msg> : " + exception.msg);null values before using objects.PluginNameTest.java e.g. PostgresPluginTest.javatestStructure() method in PostgresPluginTest.
java.