Skip to content

Custom Module Examples

Custom Module Examples

Example 1: Data Transformation Module

A simple module with no external dependencies that reformats phone numbers.

pkg/plugins/layers/v2/module_phoneformatter.go
package layers
import (
"fmt"
"regexp"
"strings"
)
type ModulePhoneFormatter struct {
*PluginModule
}
func (p *PluginModule) NewModulePhoneFormatter() *ModulePhoneFormatter {
return &ModulePhoneFormatter{PluginModule: p}
}
func (m *ModulePhoneFormatter) Type() string { return "phone-formatter" }
func (m *ModulePhoneFormatter) RequiredComponents() []string { return []string{} }
func (m *ModulePhoneFormatter) ArgsExample() map[string]any {
return map[string]any{
"phone": "+1 (555) 123-4567",
"format": "e164",
"countryCode": "1",
}
}
func (m *ModulePhoneFormatter) OutExample() map[string]any {
return map[string]any{
"formatted": "+15551234567",
"valid": true,
"national": "(555) 123-4567",
}
}
func (m *ModulePhoneFormatter) Run(params Params) Response {
input, ok := params.Args["phone"].(string)
if !ok || input == "" {
return params.Response().Fail("phone is required")
}
format, _ := params.Args["format"].(string)
countryCode, _ := params.Args["countryCode"].(string)
if countryCode == "" {
countryCode = "1"
}
// Strip non-numeric characters
digits := regexp.MustCompile(`\D`).ReplaceAllString(input, "")
if len(digits) < 10 {
return params.Response().Done(map[string]any{
"formatted": input,
"valid": false,
"national": input,
})
}
// Take last 10 digits for national number
national := digits[len(digits)-10:]
e164 := fmt.Sprintf("+%s%s", countryCode, national)
nationalFormatted := fmt.Sprintf("(%s) %s-%s", national[:3], national[3:6], national[6:])
formatted := e164
if format == "national" {
formatted = nationalFormatted
} else if format == "digits" {
formatted = strings.ReplaceAll(national, " ", "")
}
params.Log(fmt.Sprintf("Formatted phone: %s%s", input, formatted))
return params.Response().Done(map[string]any{
"formatted": formatted,
"e164": e164,
"national": nationalFormatted,
"valid": true,
})
}

Usage in flow:

{"appId": "phone-formatter", "ref": "fmt", "args": {"phone": "{{ $prev.getContact.phone }}", "format": "e164"}}

Example 2: HTTP Integration Module

A module that calls an external API using the request component.

pkg/plugins/layers/v2/module_currencyconverter.go
package layers
import (
"encoding/json"
"fmt"
"net/http"
)
type ModuleCurrencyConverter struct {
*PluginModule
}
func (p *PluginModule) NewModuleCurrencyConverter() *ModuleCurrencyConverter {
return &ModuleCurrencyConverter{PluginModule: p}
}
func (m *ModuleCurrencyConverter) Type() string { return "currency-converter" }
func (m *ModuleCurrencyConverter) RequiredComponents() []string { return []string{"request"} }
func (m *ModuleCurrencyConverter) ArgsExample() map[string]any {
return map[string]any{"amount": 100.0, "from": "USD", "to": "EUR", "apiKey": "your-key"}
}
func (m *ModuleCurrencyConverter) OutExample() map[string]any {
return map[string]any{"converted": 92.0, "rate": 0.92, "from": "USD", "to": "EUR"}
}
func (m *ModuleCurrencyConverter) Run(params Params) Response {
amount, ok := params.Args["amount"].(float64)
if !ok {
return params.Response().Fail("amount must be a number")
}
from, _ := params.Args["from"].(string)
to, _ := params.Args["to"].(string)
apiKey, _ := params.Args["apiKey"].(string)
reqComp, err := params.GetComponent("request")
if err != nil {
return params.Response().Fail("request component unavailable")
}
url := fmt.Sprintf("https://api.exchangerate.host/convert?from=%s&to=%s&amount=%f&access_key=%s", from, to, amount, apiKey)
req, _ := http.NewRequest("GET", url, nil)
resp, err := reqComp.Do(req)
if err != nil {
return params.Response().Fail("exchange rate API call failed: " + err.Error())
}
defer resp.Body.Close()
var result struct {
Result float64 `json:"result"`
Info struct { Rate float64 `json:"rate"` } `json:"info"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return params.Response().Fail("failed to parse exchange rate response")
}
return params.Response().Done(map[string]any{
"converted": result.Result,
"rate": result.Info.Rate,
"from": from,
"to": to,
"original": amount,
})
}

Example 3: AI-Powered Sentiment Analysis Module

pkg/plugins/layers/v2/module_sentimentanalyzer.go
package layers
import "fmt"
type ModuleSentimentAnalyzer struct {
*PluginModule
}
func (p *PluginModule) NewModuleSentimentAnalyzer() *ModuleSentimentAnalyzer {
return &ModuleSentimentAnalyzer{PluginModule: p}
}
func (m *ModuleSentimentAnalyzer) Type() string { return "sentiment-analyzer" }
func (m *ModuleSentimentAnalyzer) RequiredComponents() []string { return []string{"ai"} }
func (m *ModuleSentimentAnalyzer) ArgsExample() map[string]any {
return map[string]any{"text": "The service was excellent!", "model": "gpt-4o-mini"}
}
func (m *ModuleSentimentAnalyzer) OutExample() map[string]any {
return map[string]any{"sentiment": "positive", "score": 0.95, "summary": "Very positive feedback"}
}
func (m *ModuleSentimentAnalyzer) Run(params Params) Response {
text, ok := params.Args["text"].(string)
if !ok || text == "" {
return params.Response().Fail("text is required")
}
model, _ := params.Args["model"].(string)
if model == "" { model = "gpt-4o-mini" }
aiComp, err := params.GetComponent("ai")
if err != nil {
return params.Response().Fail("ai component unavailable")
}
prompt := fmt.Sprintf(`Analyze the sentiment of this text and respond with JSON only:
{"sentiment":"positive|negative|neutral","score":0.0-1.0,"summary":"brief summary"}
Text: %s`, text)
result, err := aiComp.Chat([]map[string]string{
{"role": "system", "content": "You are a sentiment analysis expert. Always respond with valid JSON."},
{"role": "user", "content": prompt},
}, map[string]any{"model": model, "temperature": 0.1})
if err != nil {
return params.Response().Fail("AI call failed: " + err.Error())
}
params.Log(fmt.Sprintf("Sentiment analysis complete for %d chars", len(text)))
return params.Response().Done(map[string]any{
"raw": result,
"text": text,
"model": model,
})
}