Advanced
Module Development
Build a Go module that compiles directly into Shiro. Modules implement a simple two-method interface and are registered via shiro add module.
Prerequisites
- • Go 1.21+
- •
shirobinary installed - • GitHub repo for publishing (or local path for development)
1
Create the Go module
bash
mkdir shiro-mymodule && cd shiro-mymodule
go mod init github.com/your-org/shiro-mymodule2
Implement the Module interface
Create mymodule/mymodule.go. Your struct must implement both Run and Metadata.
go
package mymodule
import (
"context"
"fmt"
"reflect"
)
// SchemaField mirrors modules.SchemaField — copy this to avoid importing
// shiro internal packages directly.
type SchemaField struct {
Type string `json:"type"`
Description string `json:"description"`
Required bool `json:"required"`
Default interface{} `json:"default,omitempty"`
}
type ModuleMetadata struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema map[string]SchemaField `json:"input_schema"`
OutputSchema map[string]SchemaField `json:"output_schema"`
}
type MyModule struct{}
func NewMyModule() *MyModule { return &MyModule{} }
// Run is called by Shiro for each step. Extract config from the step,
// perform your logic, and return output as map[string]interface{}.
func (m *MyModule) Run(ctx context.Context, stepCtx interface{}, step interface{}) (map[string]interface{}, error) {
cfg, err := extractConfig(step)
if err != nil {
return nil, err
}
input, _ := cfg["input"].(string)
if input == "" {
return nil, fmt.Errorf("input is required")
}
// ... your logic here ...
result := "processed: " + input
return map[string]interface{}{
"result": result,
"success": true,
}, nil
}
func (m *MyModule) Metadata() ModuleMetadata {
return ModuleMetadata{
Name: "mymodule",
Description: "Example custom module",
InputSchema: map[string]SchemaField{
"input": {Type: "string", Description: "Input value", Required: true},
},
OutputSchema: map[string]SchemaField{
"result": {Type: "string", Description: "Processed output"},
"success": {Type: "boolean", Description: "Success flag"},
},
}
}
// extractConfig pulls Config from the Shiro workflow.Step via reflection.
// This avoids importing shiro's internal/workflow package.
func extractConfig(step interface{}) (map[string]interface{}, error) {
if step == nil {
return map[string]interface{}{}, nil
}
v := reflect.ValueOf(step)
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return map[string]interface{}{}, nil
}
v = v.Elem()
}
if v.Kind() == reflect.Struct {
f := v.FieldByName("Config")
if f.IsValid() && !f.IsNil() {
if cfg, ok := f.Interface().(map[string]interface{}); ok {
return cfg, nil
}
}
return map[string]interface{}{}, nil
}
return nil, fmt.Errorf("unexpected step type %T", step)
}3
Add a module.yaml
module.yaml
name: mymodule
type: builtin
description: "Example custom module"
version: 1.0.0
source: https://github.com/your-org/shiro-mymodule
factory: NewMyModule4
Push to GitHub and register
bash
git push origin main
# In your project directory:
shiro add module github.com/your-org/shiro-mymoduleThis adds an entry to .shiro/modules/registry.yaml.
5
Rebuild Shiro
bash
# Must be run from the shiro-automation source directory
shiro buildshiro build code-generates the registry import, runs go mod tidy, and recompiles the binary. After this, mymodule is available as a step type in all workflows.
6
Use in a workflow
json
{
"id": "my_step",
"type": "mymodule",
"config": {
"input": "hello from shiro"
}
}Tips
Use reflection for config extraction
Copy the extractConfig helper from an existing module (e.g. jira-datacenter). Never import shiro internal packages — this creates a circular dependency.
Return structured output
The map returned by Run() is available to downstream steps as {{steps.<id>.output.*}}. Use consistent key names.
Handle context cancellation
Check ctx.Done() in long-running operations. Shiro may cancel the context on timeout or user interrupt.
Test locally before building
Write unit tests for Run() directly. You can mock the step using a simple struct with a Config field.
📋
Full API reference
See the API Contract for the complete Module interface, schema types, and subprocess/HTTP protocol specs.