Written for Unreal Engine 5.0 C++

Introduction

The Unreal Editor provides a general-purpose graph system that is used by Blueprints, materials, Niagara, and other graph-based features. In this reference guide, we’ll focus on K2Node from which all Blueprint nodes are derived.

There’s this fantastic tutorial on creating custom Blueprint nodes. This page is intended as a supplement to the tutorial by providing additional information and reference tables, and for that reason, I recommend everyone to read the tutorial first.

Create a Node

All Blueprint nodes should be in a UncookedOnly module. Create a new module and set the Type to UncookedOnly in the uproject or uplugin file.

"Modules": [
  {
    "Name": "MyPluginUncooked",
    "Type": "UncookedOnly",
    "LoadingPhase": "Default"
   }
]

The modules BlueprintGraph and UnrealEd need to be referenced in <ModuleName>.Build.cs.

PrivateDependencyModuleNames.AddRange(
    new string[]
    {
        "CoreUObject",
        "Engine",
        "BlueprintGraph",
        "UnrealEd"
    }
);

The simplest node that can be placed in any Blueprint graph requires creating a class derived from UK2Node and then overriding GetMenuActions to add itself to the Blueprint action database. Unreal Engine automatically detects and calls GetMenuActions on all classes derived from UK2Node.

// K2Node_Custom.h

#pragma once

#include "CoreMinimal.h"
#include "K2Node.h"
#include "K2Node_Custom.generated.h"

UCLASS()
class MYPLUGINUNCOOKED_API UK2Node_Custom : public UK2Node
{
    GENERATED_BODY()
    
public:
    virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
};
// K2Node_Custom.cpp

#include "K2Node_Custom.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintNodeSpawner.h"

void UK2Node_Custom::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
    UClass* ActionKey = GetClass();
    if (ActionRegistrar.IsOpenForRegistration(ActionKey))
    {
        UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
        check(NodeSpawner);

        ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner);
    }
}

It’ll appear at the bottom of the actions list labeled as the class name. Clicking on it will spawn a default node without any pins. A screenshot of a custom node in Blueprints. It has no pins.

Node Customization

There are various functions that can be overriden to customize how your node appears in graphs and menus.

Title

The title of the node. This can vary based on where it’s being displayed. By default, this is the class name for all title types.

virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
Title Type Description
FullTitle Displayed on the node in graphs. This can have multiple lines.
ListView A more concise, single-line title. Displayed in search results including "Find References".
EditableTitle For nodes that can be renamed, this should be the user-provided value.
MenuTitle Displayed in the Blueprint actions menu and context menus.

The category to put the node under in the Blueprint actions menu. By default or when it’s an empty value, the action is placed under the top-level category. Subcategories are created by using the pipe | character as a delimiter, i.e. "Category|Subcategory".

virtual FText GetMenuCategory() const override;

Tooltips

The tooltip appears in both the graph and the Blueprint actions menu. The tooltip heading is the smaller text above the tooltip text. It’s visible only after a node was placed. Unreal Editor uses it to display information of some status that may affect how the node behaves, such as “Replicated” when a variable is replicated.

A screenshot of a custom node with its tooltip visible.

virtual FText GetTooltipText() const override;
virtual FText GetToolTipHeading() const override;

Keywords

This defines the keywords to help users find this action using the search box in the Blueprint actions menu.

virtual FText GetKeywords() const override;

Appearance

Colors

Override these functions to set the color of the node. GetNodeTitleColor provides the color for the title bar. GetNodeBodyTintColor is not used by any Blueprint node, but it does work if you want to make your node stand out!

A series of nodes in various colors

virtual FLinearColor GetNodeTitleColor() const override;
virtual FLinearColor GetNodeBodyTintColor() const override;

Compact Node

This makes the title centered in the node and displayed in a larger font. To make the node compact, override ShouldDrawCompact to return true. By default, GetCompactNodeTitle uses the full title of the node, but you can override GetCompactNodeTitle to change this.

Examples of compact nodes (Add integers, subsystem, and boolean OR nodes)

virtual bool ShouldDrawCompact() const override;
virtual FText GetCompactNodeTitle() const override;

Bead Node

This node has no fixed location. It is always located in the middle between the input node and the output node. No Blueprint nodes use this as of Unreal Engine 5, and it appears to be a legacy option.

virtual bool ShouldDrawAsBead() const override { return true; }

Variable Node

This makes the node appear as a variable node. In other words, the title is hidden and only the pins are visible.

virtual bool DrawNodeAsVariable() const override { return true; }

Control Point / Knot

This makes the node appear as a knot like reroute nodes. Your node needs to have just one input and one output pin. You also need to provide the input and output pin (typically 0 and 1 respectively).

virtual bool ShouldDrawNodeAsControlPointOnly(int32& OutInputPinIndex, int32& OutOutputPinIndex) const override
{
    OutInputPinIndex = 0;
    OutOutputPinIndex = 1;
    return true;
}

Icons

Override GetIconAndTint to set the icon that appears on the title bar. ShowPaletteIconOnNode controls whether this icon is visible. GetCornerIcon sets the icon that appears on the top-right corner of the node.

virtual FSlateIcon GetIconAndTint(FLinearColor& OutColor) const override
{
     static const FSlateIcon Icon = FSlateIcon("EditorStyle", "GraphEditor.Default_16x");
     return Icon;
}
virtual bool ShowPaletteIconOnNode() const override { return false; }
virtual FName GetCornerIcon() const override { return TEXT("Graph.Latent.LatentIcon"); }

Can Rename Node

Set bCanRenameNode to 1 to allow users to rename the node. Alternatively, you may override GetCanRenameNode to return true. Beware that bCanRenameNode exists only when the WITH_EDITORONLY_DATA flag exists, so you’ll need to put it in between #if WITH_EDITORONLY_DATA and #endif just like in the example below.

You must also override MakeNameValidator function to provide a name validator or it will cause the editor to crash. You’ll need to store the value in a field in OnRenameNode and return it in GetNodeTitle when the title type is EditableTitle.

// K2Node_Custom.h

public:
    UK2Node_Custom();
    virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
    virtual void OnRenameNode(const FString& NewName) override;
    virtual TSharedPtr<INameValidatorInterface> MakeNameValidator() const override;

private:
    UPROPERTY()
    FString UserDefinedTitle;
// K2Node_Custom.cpp

UK2Node_Custom::UK2Node_Custom()
{
#if WITH_EDITORONLY_DATA
    bCanRenameNode = 1;
#endif
}

FText UK2Node_Custom::GetNodeTitle(ENodeTitleType::Type TitleType) const
{    
    if (TitleType == ENodeTitleType::EditableTitle && !UserDefinedTitle.IsEmpty())
    {
        return FText::FromString(UserDefinedTitle);
    }
    
    return NSLOCTEXT("ExampleGame", "NodeTitle", "Custom Node Title");
}

void UK2Node_Custom::OnRenameNode(const FString& NewName)
{
    UserDefinedTitle = NewName;
}

TSharedPtr<INameValidatorInterface> UK2Node_Custom::MakeNameValidator() const
{
    // Use the default name validator. Custom validators will need to derive from FKismetNameValidator.
    return MakeShareable(new FKismetNameValidator(GetBlueprint()));
}

Purity

To make the compiler recognize this node as being pure, override IsNodePure to return true.

virtual bool IsNodePure() const override { return true; }

Node Details

If enabled, all visible properties appear in the details window when the node is selected. A property is visible when it has any of the “Edit” or “Visible” specifiers such as EditAnywhere or VisibleAnywhere.

virtual bool ShouldShowNodeProperties() const override { return true; }
	
UPROPERTY(EditAnywhere, Category="My Custom Node")
UObject* CustomProperty;

Text Caching

Many of the customization functions mentioned in this reference guide are frequently called and generating a FText can be expensive. For this reason, it’s recommended to cache text with FNodeTextCache. Some user actions like changing the input pin connections will automatically mark the cache as dirty. If needed, you can refresh the cache with the MarkAsDirty function.

// K2Node_Custom.h

private:
    FNodeTextCache NodeTitleCache;
// K2Node_Custom.cpp

FText UK2Node_Custom::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
    if (NodeTitleCache.IsOutOfDate(this))
    {
        NodeTitleCache.SetCachedText(NSLOCTEXT("ExampleGame", "NodeTitle", "Custom Node Title"), this);
    }
    return NodeTitleCache;
}

Pins

Create Pins

AllocateDefaultPins is called whenever the node needs to be created for a multitude of reasons, i.e., spawning the node or reopening its containing Blueprint graph. Override this function and call CreatePin to create the input and output pins.

There are many overrides for CreatePin with different sets of parameters, but you’ll likely use one of the following:

UEdGraphPin* CreatePin(EEdGraphPinDirection Dir, const FName PinCategory, const FName PinName, const FCreatePinParams& PinParams = FCreatePinParams());
UEdGraphPin* CreatePin(EEdGraphPinDirection Dir, const FName PinCategory, const FName PinSubCategory, const FName PinName, const FCreatePinParams& PinParams = FCreatePinParams());
UEdGraphPin* CreatePin(EEdGraphPinDirection Dir, const FName PinCategory, UObject* PinSubCategoryObject, const FName PinName, const FCreatePinParams& PinParams = FCreatePinParams());
Parameter Description
Dir Specifies whether this pin is an input (EGPD_Input) or output (EGPD_Output) pin.
PinCategory This is the main type of the pin. Refer to the Pin Categories table below for a list of available categories.
PinSubCategory The sub-type of the pin. This is used by a small number of pin types. Refer to the Pin Categories table below for more information.
PinSubCategoryObject The sub-type of the pin using the provided object. For example, if the pin is a class reference, then this would be the class type.
PinName A name for the pin. The name needs to be unique within the node. Certain names have special meaning. Refer to the Special Pin Names table below for more information.
PinParams Additional parameters for the pin.

ContainerType Sets whether this pin is an array, set, map, or a single value.
bIsReference Sets whether to pass the value as a reference.
bIsConst Sets whether the value is immutable.
Index Sets the position of this pin in the pin list. By default, pins are appended to the list.
ValueTerminalType Sets the value type if the container type is a map.

Pin Categories

Pin categories and subcategories are defined in UEdGraphSchema_K2.

Name PinCategory PinSubCategory PinSubCategoryObject
Exec PC_Exec - -
Boolean PC_Boolean - -
Byte PC_Byte For bitmask, use PSC_Bitmask -
Integer PC_Int For bitmask, use PSC_Bitmask -
Integer64 PC_Int64 - -
Float PC_Real
Single-precision PC_Float
Double-precision PC_Double
-
Name PC_Name - -
String PC_String - -
Text PC_Text - -
Class Reference PC_Class For "self", use PSC_Self UClass* for the class type
Soft Class Reference PC_SoftClass - UClass* for the class type
Object Reference PC_Object For "self", use PSC_Self UClass* for the class type
Soft Object Reference PC_SoftObject - UClass* for the class type
Struct PC_Struct - UScriptStruct* for the struct type
Enum PC_Enum - UEnum* for the enum type
Delegate (Event) PC_Delegate - UFunction* for the function signature, or nullptr to accept any function/event
Interface* PC_Interface - UClass* for the interface type
Wildcard PC_Wildcard If the pin represents an index in a list, use PSC_Index to allow Integer, Bool, Byte, and Enum values -

*This pin accepts a reference to an object that implements the specified interface.

Reserved Pin Names

There are many “reserved” pin names defined in UEdGraphSchema_K2. Some of the common ones are:

Name Input / Output Pin Category
PN_Execute
Unnamed input exec pin
Input PC_Exec
PN_Then
Unnamed output exec pin
Output PC_Exec
PN_ReturnValue
The return object
Output PC_Object

Simple Example

Here’s an example of a simple node. Examples of more advanced pins are provided in later sections.

An example node with input and output exec pins and a floating point input pin

// K2Node_Custom.h

public:
    virtual void AllocateDefaultPins() override;
// K2Node_Custom.cpp

void UK2Node_CustomBlueprintNode::AllocateDefaultPins()
{
    Super::AllocateDefaultPins();

    // Input exec pin
    CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);

    // Output exec pin
    CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);

    // Example float input pin
    static FName ExamplePinName = TEXT("Some Value");
    CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Real, UEdGraphSchema_K2::PC_Double, ExamplePinName);
}

Wildcard Pins

There’s some work required to make wildcard pins behave as expected. If you create the pin and do nothing else, then the type of the pin will not change when another node is connected to it. This means we need to override NotifyPinConnectionListChanged to check the wildcard pin and set its type if it’s connected to another node. This function is called each time a pin is connected or disconnected, so it’s the perfect place to put our type checking logic.

In this example, we’ll create a wildcard input and output pin. Our goal is to make the output pin type match the input pin type. By the way, it’s a good practice to define the pin name outside the function but to make it simple, the pin names are inlined.

// K2Node_Custom.cpp

void UK2Node_CustomBlueprintNode::AllocateDefaultPins()
{
    Super::AllocateDefaultPins();

    CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
    CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);

    CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, TEXT("WildcardInput"));
    CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, TEXT("WildcardOutput"));
}

Override NotifyPinConnectionListChanged. Let’s begin by checking both the input and output pin and reset their type to wildcard if they’re both disconnected. We want to check for both at the same time because we don’t want to reset the pin type if either pin is already connected to another node. Next, if either wildcard pin is connected, we want to check the pin that was connected to it. If it’s also a wildcard, then we still don’t know the type and must do nothing. Finally, when the wildcard pin’s type has changed, we change all other wildcard pins to have the same type and break any pin connections that are no longer valid.

// K2Node_Custom.h

public:
    virtual void NotifyPinConnectionListChanged(UEdGraphPin* Pin) override;
// K2Node_Custom.cpp

void UK2Node_CustomBlueprintNode::NotifyPinConnectionListChanged(UEdGraphPin* Pin)
{
    Super::NotifyPinConnectionListChanged(Pin);

    UEdGraphPin* InputPin = FindPin(TEXT("WildcardInput"));
    UEdGraphPin* OutputPin = FindPin(TEXT("WildcardOutput"));
    
    if (InputPin->LinkedTo.Num() == 0 && OutputPin->LinkedTo.Num() == 0)
    {
        // Reset input pin to wildcard
        InputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
        InputPin->PinType.PinSubCategory = TEXT("");
        InputPin->PinType.PinSubCategoryObject = nullptr;

        // Reset output pin to wildcard
        OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
        OutputPin->PinType.PinSubCategory = TEXT("");
        OutputPin->PinType.PinSubCategoryObject = nullptr;
    }
    else if ((Pin == InputPin || Pin == OutputPin) && Pin->LinkedTo.Num() > 0 && Pin->LinkedTo[0]->PinType.PinCategory != UEdGraphSchema_K2::PC_Wildcard)
    {
        // Set the wildcard pin type to the connected pin type.
        Pin->PinType = Pin->LinkedTo[0]->PinType;

        // Update all wildcard pins to have the same type.
        InputPin->PinType = Pin->PinType;
        OutputPin->PinType = Pin->PinType;

        // Break any connection if it's no longer valid.
        UEdGraphSchema_K2::ValidateExistingConnections(InputPin);
        UEdGraphSchema_K2::ValidateExistingConnections(OutputPin);
    }
}

This screenshot demonstrates the logic we implemented in NotifyPinConnectionListChanged:

A screenshot of a custom node with wildcard pins

Graph Compatibility

By default, you can spawn your node in any Blueprint graph including construction scripts, functions, and macros. Override IsCompatibleWithGraph to restrict placement on certain graphs or even Blueprint classes.

// K2Node_Custom.h

virtual bool IsCompatibleWithGraph(UEdGraph const* Graph) const override;
// K2Node_Custom.cpp

bool UK2Node_Custom::IsCompatibleWithGraph(const UEdGraph* TargetGraph) const 
{
    UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(TargetGraph);
    if (!Blueprint)
    {
        return false;
    }

    // Don't let this node spawn in construction scripts
    bool bIsCompatible = FBlueprintEditorUtils::FindUserConstructionScript(Blueprint) != TargetGraph;

    return Super::IsCompatibleWithGraph(TargetGraph) && bIsCompatible;
}

You may want to use one or more of the following booleans in your implementation of IsCompatibleWithGraph:

Check for Construction Script

Construction scripts execute in the editor, so you may need this if your node is intended to not execute at edit time.

bool bIsConstructionScript = FBlueprintEditorUtils::FindUserConstructionScript(Blueprint) == TargetGraph;

Check for Event Graph

If your node expands into multiple distinct nodes (i.e. events), then you need to require it to be placed in only Event Graphs.

bool bIsEventGraph = TargetGraph->GetSchema()->GetGraphType(TargetGraph) == GT_Ubergraph;

Check for Function Graph

If your node expands into latent actions, then you need to prevent it from being placed in functions.

bool bIsFunction = TargetGraph->GetSchema()->GetGraphType(TargetGraph) == GT_Function;

Check for Macro Graph

Unlike Event Graphs, macros can only have one input node. If your node expands into multiple input nodes, then you need to prevent it from being placed in macros.

bool bIsMacro = TargetGraph->GetSchema()->GetGraphType(TargetGraph) == GT_Macro;

Require World Context

Blueprint function libraries don’t have a world context. If your node requires a world context, then you may want to check for this. Alternatively, you can expose the World Context pin as needed.

bool bHasWorldContext = Blueprint->GeneratedClass->GetDefaultObject()->ImplementsGetWorld();

Blueprint Derives From a Class

If your node is relevant only to a class, you can check to see if the Blueprint is derived from a class.

bool bIsValidSubclass = Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf(UMyClass::StaticClass());

Blueprint Implements an Interface

Unlike Blueprint->GeneratedClass->ImplementsInterface(...), the following code will detect interfaces added via the Blueprint editor — even before the Blueprint has been compiled.

TArray<UClass*> ImplementedInterfaces;
FBlueprintEditorUtils::FindImplementedInterfaces(Blueprint, true, ImplementedInterfaces);
bool bImplementsInterface = ImplementedInterfaces.Contains(UBlendableInterface::StaticClass());