# How to Create and Use Generic Nodes in Ceres Flow Visual Scripting

> Learn how to create and use generic nodes in Ceres Flow visual scripting. Define generic classes and implement templates to effectively manage type arguments and build complex workflows.

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

---

**To create generic nodes in Ceres Flow, define a generic class inheriting from `ForwardNode` or `FlowNode` with type parameters and attributes, then implement a `GenericNodeTemplate` subclass in the Editor folder to resolve type arguments and populate the type selector.**

Ceres Flow, the visual scripting framework in the akikurisu/ceres repository, provides a type-safe generic node system that lets you write node logic once and reuse it across multiple data types. Generic nodes eliminate code duplication by using C# type parameters while the editor automatically generates concrete instances at design-time based on your type selections.

## Defining the Generic Node Class

Generic node classes inherit from `ForwardNode` (for synchronous execution) or `FlowNode` and declare one or more type parameters. You define ports using `CeresPort<T>` and attributes that control the node's appearance in the graph editor.

The following example from [`Runtime/Flow/Models/Nodes/Utilities/FlowNode_CastT.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Flow/Models/Nodes/Utilities/FlowNode_CastT.cs) demonstrates a two-parameter generic node that casts between compatible types:

```csharp
using System;
using Ceres.Annotations;
using Cysharp.Threading.Tasks;
using Ceres.Graph.Flow;

namespace Ceres.Graph.Flow.Utilities
{
    [Serializable]
    [CeresGroup("Utilities")]
    [CeresLabel("Cast to {0}")]
    [CeresMetadata("style = ConstNode")]
    public class FlowNode_CastT<TFrom, TTo> : ForwardNode where TTo : TFrom
    {
        [OutputPort(false), CeresLabel("")] public NodePort exec;
        [InputPort, HideInGraphEditor, CeresLabel("Source")] public CeresPort<TFrom> sourceValue;
        [OutputPort(false), CeresLabel("Cast Failed")] public NodePort castFailed;
        [OutputPort, CeresLabel("Result")] public CeresPort<TTo> resultValue;

        protected sealed override UniTask Execute(ExecutionContext ctx)
        {
            try {
                resultValue.Value = (TTo)sourceValue.Value;
                ctx.SetNext(exec.GetT<ExecutableNode>());
            } catch (InvalidCastException) {
                ctx.SetNext(castFailed.GetT<ExecutableNode>());
            }
            return UniTask.CompletedTask;
        }
    }
}

```

**Key implementation details:**

- **Type constraints**: The `where TTo : TFrom` clause ensures compile-time safety for the cast operation.
- **Hidden input ports**: The `[HideInGraphEditor]` attribute on `sourceValue` forces type inference from a connected port rather than manual entry.
- **Dynamic labeling**: The `[CeresLabel("Cast to {0}")]` attribute includes a `{0}` placeholder that the editor replaces with the concrete type name.
- **Port definitions**: Use `[InputPort]` and `[OutputPort]` attributes with `CeresPort<T>` for typed data flow, and `NodePort` for execution flow.

## Implementing the Generic Node Template

The generic node template tells the Flow editor how to resolve type arguments and which types to display in the selection dropdown. Templates inherit from `GenericNodeTemplate` and reside in the `Editor/Flow/Templates/` directory.

Here is the template for the Cast node from [`Editor/Flow/Templates/FlowNode_CastT_Template.cs`](https://github.com/akikurisu/ceres/blob/main/Editor/Flow/Templates/FlowNode_CastT_Template.cs):

```csharp
using System;
using System.Linq;
using Ceres.Annotations;
using Ceres.Graph;
using Ceres.Utilities;

namespace Ceres.Editor.Graph.Flow
{
    internal class FlowNode_CastT_Template : GenericNodeTemplate
    {
        public override bool RequirePort() => true;

        public override Type[] GetGenericArguments(Type portValueType, Type selectArgumentType) =>
            new[] { portValueType, selectArgumentType };

        public override Type[] GetAvailableArguments(Type portValueType) =>
            CeresPort.GetAssignedPortValueTypes()
                     .Concat(ExecutableFunctionRegistry.Get().GetManagedTypes())
                     .Distinct()
                     .Where(t => t.IsAssignableTo(portValueType) && t != portValueType)
                     .ToArray();

        protected override string GetGenericNodeBaseName(string label, Type[] argumentTypes) =>
            string.Format(label, CeresLabel.GetTypeName(argumentTypes[1]));
    }
}

```

**Critical template methods:**

- **`RequirePort()`**: Returns `true` when the template needs a connected port to infer the first type parameter (`TFrom`). Return `false` for nodes like `GetComponent` where the user selects the type manually.
- **`GetGenericArguments`**: Constructs the type array that instantiates the generic class. For the Cast node, it returns `[portValueType, selectArgumentType]`.
- **`GetAvailableArguments`**: Returns the list of types displayed in the editor's dropdown. The example filters for types assignable to `TFrom` but excluding the source type itself.
- **`GetGenericNodeBaseName`**: Formats the final node label by substituting placeholders in the `[CeresLabel]` with actual type names.

## Adding Generic Nodes to a Flow Graph

Once you have created the node class and template, you can use them in the visual editor:

1. Open the Flow editor through **Window → Ceres Flow**.
2. Locate your node in the palette (grouped by the `[CeresGroup]` attribute).
3. Drag the node onto the canvas.
4. Connect a port to the hidden input (if `RequirePort()` returns `true`) to establish `TFrom`.
5. Click the **Select Type** dropdown in the node inspector to choose the concrete type for `TTo`.
6. The node's label updates automatically (e.g., "Cast to Vector2") and the concrete node is compiled for execution.

## Practical Examples of Generic Nodes

Beyond the Cast node, Ceres Flow includes several built-in generic nodes demonstrating different parameter patterns.

### GetComponent<T> Node

The `FlowNode_GetComponentT<T>` node retrieves a component from a GameObject using a single type parameter. Located in [`Runtime/Flow/Models/Nodes/GameObject/FlowNode_GetComponentT.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Flow/Models/Nodes/GameObject/FlowNode_GetComponentT.cs), it uses the constraint `where T : Component`. Its template ([`FlowNode_GetComponentT_Template.cs`](https://github.com/akikurisu/ceres/blob/main/FlowNode_GetComponentT_Template.cs)) sets `RequirePort()` to `false` and returns all non-abstract Component types from `GetAvailableArguments`, allowing users to select any component type without needing a source port connection.

### FindObjectOfType<T> Node

The `FlowNode_FindObjectOfTypeT<T>` node (in [`Runtime/Flow/Models/Nodes/Utilities/FlowNode_FindObjectOfTypeT.cs`](https://github.com/akikurisu/ceres/blob/main/Runtime/Flow/Models/Nodes/Utilities/FlowNode_FindObjectOfTypeT.cs)) searches the scene for a specific Unity Object type. With the constraint `where T : Object`, it uses a template that provides every concrete Unity Object type in the dropdown. The node includes a "Not Found" output port for handling missing objects safely.

## Best Practices for Generic Node Development

- **Apply type constraints**: Use `where` clauses to restrict `T` to valid types (e.g., `Component` or `TFrom`), which narrows the editor dropdown and prevents runtime errors.
- **Hide inferred ports**: Apply `[HideInGraphEditor]` to ports where the type should be determined by a connection, preventing manual type mismatches.
- **Cache type lookups**: In `GetAvailableArguments()`, cache expensive reflection calls (like `CeresPort.GetAssignedPortValueTypes()`) in static fields to improve editor performance.
- **Use descriptive labels**: Include `{0}`, `{1}` placeholders in `[CeresLabel]` attributes so instantiated nodes display their concrete types directly on the canvas.
- **Handle null cases**: Always provide output ports for failure states (like "Cast Failed" or "Not Found") to prevent exceptions when objects are missing.

## Summary

- Generic nodes consist of a runtime class (inheriting `ForwardNode` or `FlowNode`) and an editor template (inheriting `GenericNodeTemplate`).
- Place node classes in `Runtime/Flow/Models/Nodes/` and templates in `Editor/Flow/Templates/`.
- Implement `RequirePort()`, `GetGenericArguments()`, `GetAvailableArguments()`, and `GetGenericNodeBaseName()` in your template to control type resolution.
- Use `[HideInGraphEditor]` on ports that should infer types from connections, and `[CeresLabel]` with placeholders for dynamic naming.
- Reference the Cast, GetComponent, and FindObjectOfType implementations as canonical patterns for multi-parameter and single-parameter generic nodes.

## Frequently Asked Questions

### What is the purpose of a generic node template in Ceres Flow?

The generic node template bridges the visual editor and the runtime generic class. It tells the editor which types to display in selection dropdowns, how to resolve type parameters from port connections or user input, and how to format the node's display name. Without a template, the editor cannot instantiate concrete versions of generic nodes.

### How does Ceres Flow infer generic type arguments from ports?

When `RequirePort()` returns `true` in the template, the editor reads the type of the connected port and passes it as the `portValueType` argument to `GetGenericArguments()`. The method then constructs the type array (e.g., `[TFrom, TTo]`) used to instantiate the generic class. The `[HideInGraphEditor]` attribute on the corresponding `CeresPort<T>` field prevents manual type entry, ensuring the connection drives the type inference.

### Can I create generic nodes with multiple type parameters?

Yes, Ceres Flow supports multiple type parameters as demonstrated by `FlowNode_CastT<TFrom, TTo>`. In the template's `GetGenericArguments` method, return a `Type[]` array where the order matches the generic class declaration. Use `argumentTypes[0]`, `argumentTypes[1]`, etc., in `GetGenericNodeBaseName` to reference specific type names for labeling.

### Where should generic node files be located in the project structure?

Place the runtime node class (the generic logic) in a subdirectory of `Runtime/Flow/Models/Nodes/` following the existing organization (e.g., `Utilities/` or `GameObject/`). Place the editor template in `Editor/Flow/Templates/`. This separation ensures that editor-only code does not compile into builds while maintaining the correct assembly references between runtime and editor components.