# Module Development Tips Thread

**URL:** <https://forum.inductiveautomation.com/t/module-development-tips-thread/106379>\
**Category:** Module Development\
**Created:** [May 17, 2025, 11:28am UTC](https://forum.inductiveautomation.com/t/module-development-tips-thread/106379 "2025-05-17T11:28:20Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![David\_Stone](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/david_stone/32/79749_2.png) [@David\_Stone](https://forum.inductiveautomation.com/u/David_Stone)\
**Post date:** [May 17, 2025, 11:28am UTC](https://forum.inductiveautomation.com/t/module-development-tips-thread/106379/1 "2025-05-17T11:28:20Z")

</div>

Module development is a step above developing inside the Ignition Designer.

In this thread we can add any tips and tricks we have learned along the way to make it a more approachable learning curve.

I have made a bunch of my posts into Wiki posts, so if you have the permissions, you can add/edit as needed.

---

<div class="post-metadata">

**Author:** ![David\_Stone](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/david_stone/32/79749_2.png) [@David\_Stone](https://forum.inductiveautomation.com/u/David_Stone)\
**Post date:** [May 17, 2025, 11:31am UTC](https://forum.inductiveautomation.com/t/module-development-tips-thread/106379/2 "2025-05-17T11:31:07Z")

</div>

**Package Names**

~~IA~~ _Java_ has [guidelines](https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html) for the package names you build your modules under. They should be your company domain name reversed. _Note the rules for "legalizing" a domain name._

`http://domain.com`  
Becomes:  
`com.domain.modulename`

After that should be a package name that reflects the scope for the target jar, if applicable:

`com.domain.modulename.gateway`  
`com.domain.modulename.client`  
`com.domain.modulename.designer`

Packages destined for multiple scopes usually are placed under  
`com.domain.modulename.common`

---

<div class="post-metadata">

**Author:** ![David\_Stone](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/david_stone/32/79749_2.png) [@David\_Stone](https://forum.inductiveautomation.com/u/David_Stone)\
**Post date:** [May 17, 2025, 11:49am UTC](https://forum.inductiveautomation.com/t/module-development-tips-thread/106379/3 "2025-05-17T11:49:53Z")

</div>

**Code Signing**

When you are developing a module, you will not be able to run it outside of developer mode without signing it. Code signing is a process where you will use a cryptographic certificate similar to an SSL certificate to sign a module, which enables Ignition to verify that the module is what it claims to be in installation.  
A code signing certificate is essentially an SSL or TLS certificate similar to what you need to install on a webserver to enable https:// traffic. The only difference is that when you generate the certificate signing request, you indicate that the certificate is to be used for code signing.

> **Enabling Developer Mode**
>
> In your ignition.conf, scroll down to the area with a number of entries similar to
> 
> ```python
> wrapper.java.additional.1=
> wrapper.java.additional.2=
> wrapper.java.additional.3=
> wrapper.java.additional.4=
> wrapper.java.additional.5=
> wrapper.java.additional.6=
> 
> ```
> 
> Add the following entry:
> 
> ```python
> wrapper.java.additional.7=-Dignition.allowunsignedmodules=true
> 
> ```
> 
> This allows the gateway to load unsigned modules.  
> Use this at your own risk on non-production gateways for development purposes only.

When you need to migrate to signed code, you have two options available:

> **Self-Signed**
>
> Self signed code uses a certificate generated by you to sign the module. You can generate a code signing certificate yourself with minimal effort, and then sign your own modules ready for distribution.

> **CA Signed**
>
> CA signing uses a "CA" or Certificate Authority, who have proven security credentials, to prove that you are who you claim to be and that the code signed with your credentials is able to be proved to be from you, and has not been tampered with.
> 
> CA Signing is not necessary, but can appear to some people/organisations as more trustworthy and reliable.
> 
> CA Signing is expensive.

Once you have a ceritificate, download Keystore Explorer:

> **[KeyStore Explorer](https://keystore-explorer.org/)**
>
> KeyStore Explorer is an open source GUI replacement for the Java command-line utilities keytool and jarsigner. KeyStore Explorer presents their functionality, and more, via an intuitive graphical user interface.

Create an empty JKS keystore and upload your certificate pair to this keystore.  
Save the keystore then follow the guide here:

> **[Sign a Module | Ignition SDK Programmer's Guide](https://www.sdk-docs.inductiveautomation.com/docs/getting-started/create-a-module/module-signing/)**
>
> An Ignition Module must be signed before it can be installed on a system that is not in developer mode.

When you have run this command, you will end up with a signed module ready to install on any gateway necessary.

---

<div class="post-metadata">

**Author:** ![David\_Stone](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/david_stone/32/79749_2.png) [@David\_Stone](https://forum.inductiveautomation.com/u/David_Stone)\
**Post date:** [May 17, 2025, 11:57am UTC](https://forum.inductiveautomation.com/t/module-development-tips-thread/106379/4 "2025-05-17T11:57:11Z")

</div>

**Trial Mode**

You as the developer are expected to enable and disable your module functions based on the two hour trial period. This applies to free modules as well as paid.

~~To enable this there are functions like below to get the current license states:~~

 

~~`gatewayContext.getLicenseManager().getPlatformLicenseState()`~~

(Don't use those.)

Your module hook will be notified at startup whether it is licensed and/or whether trial mode is active. And will be notified dynamically when/if the licensing state changes. Your module, if it cares, should capture this state in a gateway hook field that the rest of your module can interrogate. (Either make the field static with a static getter, or pass your hook instance into your module's infrastructure.)

---

<div class="post-metadata">

**Author:** ![kgamble](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kgamble/32/8019_2.png) [@kgamble](https://forum.inductiveautomation.com/u/kgamble)\
**Post date:** [May 17, 2025, 12:47pm UTC](https://forum.inductiveautomation.com/t/module-development-tips-thread/106379/5 "2025-05-17T12:47:41Z")

</div>

Here is a fairly thorough Perspective Component Module focused repo I made that has a pretty wide span of tips and tools.

Helpful as a starter tutorial [GitHub - keith-gamble/example-perspective-component-module: An Example Repository for building Ignition Perspective Component Modules](https://github.com/keith-gamble/example-perspective-component-module)

Digging in, if anyone finds gaps, has questions, etc. I am more than happy to add information and examples into its docs.

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 17, 2025, 3:55pm UTC](https://forum.inductiveautomation.com/t/module-development-tips-thread/106379/6 "2025-05-17T15:55:57Z")

</div>

**Monorepo for Multiple Modules**

This repository is an example demonstrating multiple Ignition modules in the same repository, with shared library support.

Everyone's needs are different. But in our experience as a small team working on multiple related modules, the monorepo architecture has allowed us to move quickly while sharing internal resources in [Embr](https://github.com/mussonindustrial/embr).

> **[GitHub - benmusson/ignition-module-monorepo-example: An example demonstrating multiple Ignition modules...](https://github.com/benmusson/ignition-module-monorepo-example)**
>
> An example demonstrating multiple Ignition modules in the same repository.

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 17, 2025, 4:01pm UTC](https://forum.inductiveautomation.com/t/module-development-tips-thread/106379/7 "2025-05-17T16:01:10Z")

</div>

**Powershell Script for Generating Signing Certificate and `gradle.properties`**

It's not fantastic, but since I only run it a couple times per year I haven't felt the need to improve it. You will definitely need to adjust the Java install location.

This script takes:

1. A DNS name.
2. A number of years before expiration.

And generates (in a `signature` folder):

1. A password protected signing certificate.
2. Adds it to a password protected keystore.
3. Creates a corresponding `gradle.properties` that can be used with IA's module creation plugin.

```powershell
$CertStoreLocation = "cert:\CurrentUser\My"
$JavaHome = 'C:\Program Files\Java\jdk-18.0.1\bin'
$FolderName = "signature"

$FolderPath = "$PSScriptRoot\$FolderName"
New-Item $FolderPath -ItemType Directory

$InfoFile = ($FolderPath + "\keystoreinfo.txt")
$GradleProps = ($FolderPath + "\gradle.properties")

$DnsName = Read-Host "Certificate DNS Name"
$YearsBeforeExp = Read-Host "Years before expiration"

Add-Type -AssemblyName System.Web
$CertPassword = [System.Web.Security.Membership]::GeneratePassword(32,4)
$CertPasswordSecure = ConvertTo-SecureString $CertPassword –asplaintext –force 
$KeystorePassword = [System.Web.Security.Membership]::GeneratePassword(32,4)
$KeystoreSecure = ConvertTo-SecureString $CertPassword –asplaintext –force 

$RootFileName = $DnsName.Replace(' ','_')
$RootFilePath = "$FolderPath\$RootFilePath"

$KeystoreFileName = ($RootFileName + "_keystore.jks")
$KeystoreFilePath = "$FolderPath\$KeystoreFileName"

Write-Output "Creating new certificate with DnsName '$DnsName'..."
$Cert = New-SelfSignedCertificate -DnsName $DnsName -Type CodeSigning -CertStoreLocation $CertStoreLocation -NotAfter (Get-Date).AddYears($YearsBeforeExp)

$FileName = "$RootFileName.p7b"
$FilePath = "$FolderPath\$FileName"
Write-Output "Exporting certificate to '$FileName'..."
$Results = Export-Certificate -Cert $Cert -Type P7B -FilePath "$FilePath"

$FileName = "$RootFileName.pfx"
$FilePath = "$FolderPath\$FileName"
Write-Output "Exporting certificate to '$FileName'..."
$Results = Export-PfxCertificate -Cert $Cert -FilePath "$FilePath" -Password $CertPasswordSecure

Write-Output "Removing certificate from personal store..."
$Cert | Remove-Item

$FileName = "$RootFileName.pfx"
Write-Output "Converting '$FileName' to '$KeystoreFileName'..."
$FilePath = "$FolderPath\$FileName"
$Results = Start-Process -FilePath "$JavaHome\keytool.exe" -ArgumentList "-importkeystore -srckeystore .\$FolderName\$FileName -srcstoretype pkcs12 -destkeystore .\$FolderName\$KeystoreFileName -deststoretype JKS -srcstorepass $CertPassword -storepass $KeystorePassword -keypass $CertPassword" -NoNewWindow -PassThru -Wait

Write-Output "Reading key aliases..."
$Results = Start-Process -FilePath "$JavaHome\keytool.exe" -ArgumentList "-list -v -keystore .\$FolderName\$KeystoreFileName -storepass $KeystorePassword" -NoNewWindow -PassThru -RedirectStandardOutput $InfoFile -Wait
$AliasInfo = @(Get-Content -Path $InfoFile | Where-Object { $_.Contains("Alias") })

$AliasName = "ALIAS NOT FOUND"
$line = $AliasInfo[0]
$start = $line.IndexOf(":") + 2
$length = $line.Length - $start
$Alias = $line.Substring($start, $length)
$AliasName = $Alias.Trim()
Write-Output "Alias '$AliasName' found."
#Start-Process -FilePath "$JavaHome\keytool.exe" -ArgumentList "-changealias -alias $Alias -destalias signing_cert -keypass $CertPassword -keystore $RootFileName.jks -storepass $KeystorePassword" -Wait

Write-Output "Generating gradle.properties..."
$content = @"
ignition.signing.certFile= /$FolderName/$RootFileName.p7b
ignition.signing.certPassword= $CertPassword
ignition.signing.certAlias= $AliasName
ignition.signing.keystoreFile= /$FolderName/$KeystoreFileName
ignition.signing.keystorePassword= $KeystorePassword
"@

Add-Content $GradleProps $content

```

---

<div class="post-metadata">

**Author:** ![David\_Stone](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/david_stone/32/79749_2.png) [@David\_Stone](https://forum.inductiveautomation.com/u/David_Stone)\
**Post date:** [May 20, 2025, 3:31am UTC](https://forum.inductiveautomation.com/t/module-development-tips-thread/106379/8 "2025-05-20T03:31:20Z")

</div>

**Free Modules and Maker Modules**  
Making your module free and maker compatible is done by overriding these interface classes:

```java
    @Override
    public boolean isMakerEditionCompatible() {
        return true;
    }

    @Override
    public boolean isFreeModule() {
        return true;
    }

```
