custom-builtin-functions

chainloop-dev's avatarfrom chainloop-dev

Create a custom builtin function to be used in the Rego policy engine

⭐517stars🔀41forks📁View on GitHub🕐Updated Dec 26, 2025

When & Why to Use This Skill

This Claude skill streamlines the process of extending the Open Policy Agent (OPA) Rego policy engine by generating custom built-in functions in Go. It automates the creation of boilerplate code for function registration, argument validation, and implementation logic, ensuring that custom policy extensions are performant, well-documented, and compatible with the Chainloop ecosystem.

Use Cases

  • Custom Policy Logic: Implementing complex business rules or data transformations that are difficult to express in native Rego syntax.
  • External Service Integration: Creating non-restrictive built-in functions to fetch real-time data from third-party APIs or internal microservices during policy evaluation.
  • Security & Compliance: Developing specialized cryptographic checks or proprietary validation logic to enforce organizational security standards.
  • Infrastructure-as-Code (IaC) Validation: Extending policy engines to interact with specific cloud provider metadata or internal inventory systems for more granular resource auditing.
namecustom-builtin-functions
descriptionCreate a custom builtin function to be used in the Rego policy engine

Policy Engine Extension

The OPA/Rego policy engine supports custom built-in functions written in Go.

Adding Custom Built-ins:

  1. Create Built-in Implementation (e.g., pkg/policies/engine/rego/builtins/myfeature.go):
package builtins

import (
    "github.com/open-policy-agent/opa/ast"
    "github.com/open-policy-agent/opa/topdown"
    "github.com/open-policy-agent/opa/types"
)

const myFuncName = "chainloop.my_function"

func RegisterMyBuiltins() error {
    return Register(&ast.Builtin{
        Name: myFuncName,
        Description: "Description of what this function does",
        Decl: types.NewFunction(
            types.Args(types.Named("input", types.S).Description("this is the input")),
            types.Named("result", types.S).Description("this is the result"),
        ),
    }, myFunctionImpl)
}

func myFunctionImpl(bctx topdown.BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
    // Extract arguments
    input, ok := operands[0].Value.(ast.String)
    if !ok {
        return fmt.Errorf("input must be a string")
    }

    // Implement logic
    result := processInput(string(input))

    // Return result
    return iter(ast.StringTerm(result))
}

// Autoregisters on package load
func init() {
    if err := RegisterMyBuiltins(); err != nil {
        panic(fmt.Sprintf("failed to register built-ins: %v", err))
    }
}
  1. Use in Policies (*.rego):
package example
import rego.v1

result := {
    "violations": violations,
    "skipped": false
}

violations contains msg if {
    output := chainloop.my_function(input.value)
    output != "expected"
    msg := "Function returned unexpected value"
}

Guidelines:

  • Use chainloop.* namespace for all custom built-ins
  • Functions that call third party services should be marked as non-restrictive by adding the NonRestrictiveBuiltin category to the builtin definition
  • Always implement proper error handling and return meaningful error messages
  • Use context from BuiltinContext for timeout/cancellation support
  • Document function signatures and behavior in the Description field and parameter definitions