# How to Create Custom Executable Nodes with ExecutableFunctionAttribute in Ceres Flow

> Learn how to create custom executable nodes using ExecutableFunctionAttribute in Ceres Flow. Transform C# methods into discoverable visual scripting nodes easily.

- Repository: [AkiKurisu/ceres](https://github.com/akikurisu/ceres)
- Tags: how-to-guide
- Published: 2026-02-24

---

**[ExecutableFunctionAttribute]** turns ordinary C# methods into visual scripting nodes by marking them with metadata that the Ceres Flow editor automatically discovers and exposes as searchable graph nodes.

The **akikurisu/ceres** repository provides a powerful visual scripting framework that eliminates boilerplate when extending node libraries. By applying `ExecutableFunctionAttribute` to your methods, you enable the `ExecutableFunctionRegistry` to index them at editor startup and generate corresponding Flow nodes without writing custom node classes.

## Understanding the ExecutableFunctionAttribute Architecture

Ceres Flow uses a multi-layered reflection system to transform attributed methods into graph nodes. The architecture separates metadata definition from runtime discovery.

### Core Components

The system relies on three primary classes defined in the Runtime and Editor assemblies:

- **`ExecutableFunctionAttribute`** – Defined in [`Runtime/Flow/Annotations/ExecutableFunctionAttribute.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Flow/Annotations/ExecutableFunctionAttribute.cs), this attribute stores metadata flags like `IsScriptMethod` and `ExecuteInDependency` that control node behavior and appearance.

- **`ExecutableReflection`** – Located in [`Runtime/Flow/Models/ExecutableReflection.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Flow/Models/ExecutableReflection.cs), this wrapper extracts method signatures, parameter lists, and return types to build the node UI description and port mappings.

- **`ExecutableFunctionRegistry`** – Found in [`Editor/Flow/ExecutableFunctionRegistry.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Flow/ExecutableFunctionRegistry.cs), this editor-only singleton scans loaded assemblies at startup, caching discovered methods in `_staticFunctions`, `_instanceFunctionTables`, and `_retargetFunctionTables` collections.

### How the Registry Discovers Methods

When the Unity editor loads, `ExecutableFunctionRegistry.Get()` enumerates runtime-referenced assemblies plus those defined in **Project Settings → Ceres → Flow Settings**. For types deriving from `ExecutableFunctionLibrary`, the registry gathers static methods via source generation (see [`Runtime/CodeGen/ExecutableReflectionILPP.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/CodeGen/ExecutableReflectionILPP.cs)). For all other types, it calls `ExecutableReflection.GetInstanceExecutableFunctions(type)` using `methodInfo.GetCustomAttribute<ExecutableFunctionAttribute>()` to identify candidates.

## Creating Instance Method Nodes

The simplest approach attaches executable functions directly to MonoBehaviour or ScriptableObject components. No additional boilerplate or base classes are required.

```csharp
using Ceres.Graph.Flow.Annotations;
using UnityEngine;

public class MyComponent : MonoBehaviour
{
    [ExecutableFunction]
    public void DoSomething(int arg1, float arg2)
    {
        Debug.Log($"Arg1={arg1}, Arg2={arg2}");
    }
}

```

After compilation, the Flow graph editor displays a node named **Do Something** with two input ports corresponding to the method parameters. The node appears in the search window under the component's namespace, ready for connection to other graph logic.

## Creating Static Method Nodes with ExecutableFunctionLibrary

For utility functions or engine wrappers, static methods provide better performance through source generation. You must declare them inside a **partial** class inheriting from `ExecutableFunctionLibrary`.

```csharp
using Ceres.Graph.Flow.Annotations;
using Ceres.Graph.Flow;
using UnityEngine;

public partial class UnityExecutableFunctionLibrary : ExecutableFunctionLibrary
{
    [ExecutableFunction(IsScriptMethod = true, IsSelfTarget = true), CeresLabel("Get Name")]
    public static string Flow_UObjectGetName(UObject target)
    {
        return target.name;
    }

    [ExecutableFunction]
    public static UObject Flow_FindObjectOfType(
        [ResolveReturn] SerializedType<UObject> type)
    {
        return UObject.FindObjectOfType(type);
    }
}

```

**Key attribute properties:**

- **`IsScriptMethod = true`** – Designates the first parameter as the script target, organizing the node under that type's context menu.
- **`IsSelfTarget = true`** – Automatically supplies the graph owner as the first argument when used within a UObject's Flow graph.
- **`CeresLabel`** – Overrides the auto-generated node name (which defaults to the method name).

The source generator in [`ExecutableReflectionILPP.cs`](https://github.com/akikurisu/ceres/blob/main/ExecutableReflectionILPP.cs) replaces runtime reflection with direct delegate calls for these static methods, eliminating performance overhead during graph execution.

## Invoking Executable Functions from Custom Nodes

When you need additional logic beyond a direct method call, create a custom node class that retrieves the `MethodInfo` from the registry and invokes it manually.

```csharp
using System.Reflection;
using Ceres.Annotations;
using Ceres.Graph.Flow;
using Ceres.Editor.Graph.Flow;
using System.Linq;

[Serializable]
[CeresGroup("Utilities")]
[CeresLabel("Call GetName")]
public class FlowNode_CallGetName : FlowNode
{
    [InputPort, CeresLabel("Target")]
    public CeresPort<UObject> target = new CeresPort<UObject>();

    [OutputPort, CeresLabel("Name")]
    public CeresPort<string> result = new CeresPort<string>();

    protected override void LocalExecute(ExecutionContext ctx)
    {
        var method = ExecutableFunctionRegistry.Get()
                     .GetStaticFunctions()
                     .First(m => m.Name == nameof(UnityExecutableFunctionLibrary.Flow_UObjectGetName));

        var name = (string)method.Invoke(null, new object[] { target.Value });
        result.Value = name;
    }
}

```

This pattern allows you to wrap executable functions with validation, logging, or error handling while maintaining the underlying method's signature and port structure.

## Summary

- **`ExecutableFunctionAttribute`** marks C# methods for automatic node generation in the Ceres Flow editor.

- **Instance methods** require only the attribute and appear automatically for components inheriting from `MonoBehaviour` or `ScriptableObject`.
- **Static methods** must reside in a `partial` class extending `ExecutableFunctionLibrary` to leverage source generation and avoid reflection costs.
- The **`ExecutableFunctionRegistry`** caches all discovered methods at editor startup in [`Editor/Flow/ExecutableFunctionRegistry.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Flow/ExecutableFunctionRegistry.cs).
- Custom nodes can invoke registry methods via reflection or directly when extending `FlowNode`, `ForwardNode`, or `ExecutableNode` base classes.

## Frequently Asked Questions

### What is the difference between instance and static executable functions?

Instance functions are methods defined on components like MonoBehaviours that the registry discovers via `ExecutableReflection.GetInstanceExecutableFunctions(type)` in [`Runtime/Flow/Models/ExecutableReflection.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Flow/Models/ExecutableReflection.cs). Static functions must be placed in a class extending `ExecutableFunctionLibrary` and are processed by the source generator in [`Runtime/CodeGen/ExecutableReflectionILPP.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/CodeGen/ExecutableReflectionILPP.cs) to eliminate runtime reflection overhead.

### How does Ceres Flow handle method parameters and return values?

The `ExecutableReflection` class parses each method's `MethodInfo` to generate input ports for parameters and output ports for return values. In [`ExecutableFunctionRegistry.cs`](https://github.com/akikurisu/ceres/blob/main/ExecutableFunctionRegistry.cs), the system maps primitive types, Unity objects, and special types like `SerializedType<T>` (marked with `[ResolveReturn]`) to corresponding Ceres port types automatically.

### Can I rename the generated node without changing the method name?

Yes. Apply the `[CeresLabel("Desired Name")]` attribute alongside `[ExecutableFunction]`. According to the implementation in [`Runtime/Flow/Annotations/ExecutableFunctionAttribute.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Flow/Annotations/ExecutableFunctionAttribute.cs), this overrides the default naming convention that uses the C# method name.

### Why must static method libraries be declared as partial classes?

The **partial** keyword allows the source generator to inject registration code into your class at compile time. This generated code, handled by [`ExecutableReflectionILPP.cs`](https://github.com/akikurisu/ceres/blob/main/ExecutableReflectionILPP.cs), creates direct delegates for static methods, replacing the reflection-based invocation path with high-performance direct calls that the `ExecutableFunctionRegistry` caches in `_staticFunctions`.