Can't see my custom Niagara Data Interface in "Add User Parameter → Data Interface" menu despite proper setup and registration

Hey everyone, I’m trying to create a custom Niagara Data Interface in UE5 for sampling wind data from a custom UWindVectorField class. I’ve followed all the usual steps, but the data interface is not showing up in the “Add User Parameter → Data Interface” dropdown in Niagara.

Basically, what I am trying to do is show my wind simulation field using Niagara particles like Peter Sikachev is doing in this Youtube video: https://youtu.be/HTlALlfz_T0?t=841

Here’s what I’ve done so far:

Created two files:

  • NiagaraWindFieldDataInterface.h:
  • NiagaraWindFieldDataInterface.cpp:

What these files include:

  • NiagaraWindFieldDataInterface.h:
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "NiagaraDataInterface.h"
#include "WindVectorField.h"
#include "NiagaraWindFieldDataInterface.generated.h"

UCLASS(EditInlineNew, BlueprintType, Category = "Wind", meta = (DisplayName = "Wind Field"))
class EMBERFLIGHT_API UNiagaraWindFieldDataInterface : public UNiagaraDataInterface
{
    GENERATED_BODY()
public:
    UFUNCTION(BlueprintCallable, Category="Wind")
    FVector GetZeroWind() const { return FVector::ZeroVector; }

    UNiagaraWindFieldDataInterface();

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Wind")
    TObjectPtr<UWindVectorField> WindField;
    virtual void GetFunctions(TArray<FNiagaraFunctionSignature>& OutFunctions) override;
    virtual void GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction& OutFunc) override;
    virtual bool Equals(const UNiagaraDataInterface* Other) const override;
    virtual bool CanExecuteOnTarget(ENiagaraSimTarget Target) const override;
};
  • NiagaraWindFieldDataInterface.cpp:
// Fill out your copyright notice in the Description page of Project Settings.


#include "NiagaraWindFieldDataInterface.h"
#include "NiagaraTypes.h"
#include "NiagaraModule.h"
#include "NiagarafunctionLibrary.h"
#include "Modules/ModuleManager.h"

#define LOCTEXT_NAMESPACE "NiagaraWindFieldDI"

UNiagaraWindFieldDataInterface::UNiagaraWindFieldDataInterface() {}

struct FSampleWindAtLocation
{
    static void Exec(FVectorVMExternalFunctionContext& Context, const UWindVectorField* WindField)
    {
        VectorVM::FUserPtrHandler<const UWindVectorField> FieldHandler(Context);
        VectorVM::FExternalFuncInputHandler<float> X(Context);
        VectorVM::FExternalFuncInputHandler<float> Y(Context);
        VectorVM::FExternalFuncInputHandler<float> Z(Context);

        VectorVM::FExternalFuncRegisterHandler<float> OutX(Context);
        VectorVM::FExternalFuncRegisterHandler<float> OutY(Context);
        VectorVM::FExternalFuncRegisterHandler<float> OutZ(Context);

        for (int32 i = 0; i < Context.GetNumInstances(); ++i)
        {
            FVector WorldPos(X.GetAndAdvance(), Y.GetAndAdvance(), Z.GetAndAdvance());
            FVector Velocity = WindField ? WindField->SampleWindAtPosition(WorldPos) : FVector::ZeroVector;

            *OutX.GetDestAndAdvance() = Velocity.X;
            *OutY.GetDestAndAdvance() = Velocity.Y;
            *OutZ.GetDestAndAdvance() = Velocity.Z;
        }
    }
};

void UNiagaraWindFieldDataInterface::GetFunctions(TArray<FNiagaraFunctionSignature>& OutFunctions)
{
    FNiagaraFunctionSignature Sig;
    Sig.Name = FName(TEXT("SampleWindAtLocation"));
    Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Wind Field")));
    Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("X")));
    Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Y")));
    Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Z")));

    Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("OutX")));
    Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("OutY")));
    Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("OutZ")));

    Sig.SetDescription(LOCTEXT("SampleWindDesc", "Sample wind velocity at a given world position"));
    Sig.bMemberFunction = true;

    OutFunctions.Add(Sig);
}

void UNiagaraWindFieldDataInterface::GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction& OutFunc)
{
    if (BindingInfo.Name == TEXT("SampleWindAtLocation"))
    {
        OutFunc = FVMExternalFunction::CreateLambda([this](FVectorVMExternalFunctionContext& Context)
            {
                FSampleWindAtLocation::Exec(Context, WindField);
            });
    }
}

bool UNiagaraWindFieldDataInterface::Equals(const UNiagaraDataInterface* Other) const
{
    const UNiagaraWindFieldDataInterface* OtherTyped = CastChecked<UNiagaraWindFieldDataInterface>(Other);
    return OtherTyped && OtherTyped->WindField == WindField;
}

bool UNiagaraWindFieldDataInterface::CanExecuteOnTarget(ENiagaraSimTarget Target) const
{
    return true;
}
#undef LOCTEXT_NAMESPACE

Some explanation for the code:

  • Added a dummy UFUNCTION (FVector GetZeroWind()) marked as BlueprintCallable, just to ensure it’s exposed for reflection.

  • The WindField is exposed via:

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = “Wind”)

    TObjectPtr WindField;

Just to be sure that the data interface is registered successfully, I have also tried to list it in the console upon begin play:

My_Game.cpp (this is the game module):

include "EmberFlight.h"
#include "NiagaraDataInterface.h"
#include "NiagaraWindFieldDataInterface.h"
#include "NiagaraDataInterface.h"
#include "UObject/UObjectIterator.h"
#include "Modules/ModuleManager.h"

IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, EmberFlight, "EmberFlight" );

void FEmberFlightModule::StartupModule()
{
    UE_LOG(LogTemp, Warning, TEXT("Wind DI class is: %s"), *UNiagaraWindFieldDataInterface::StaticClass()->GetName());
}

void FEmberFlightModule::ShutdownModule()
{
    
}

void ListAllNiagaraInterfaces()
{
    static const FName TempClassName = UNiagaraWindFieldDataInterface::StaticClass()->GetFName();

    for (TObjectIterator<UClass> It; It; ++It)
    {
        if (It->IsChildOf(UNiagaraDataInterface::StaticClass()) && !It->HasAnyClassFlags(CLASS_Abstract))
        {
            UE_LOG(LogTemp, Warning, TEXT("Found DI: %s"), *It->GetName());
        }
    }
}

My_Game_GameMode.cpp:

// Copyright Epic Games, Inc. All Rights Reserved.

#include "EmberFlightGameMode.h"
#include "EmberFlight.h"
#include "UObject/ConstructorHelpers.h"

AEmberFlightGameMode::AEmberFlightGameMode()
{
	// set default pawn class to our Blueprinted character
	static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("Blueprint'/Game/Blueprints/BP_PhoenixCharacter.BP_PhoenixCharacter'"));
	if (PlayerPawnBPClass.Class != NULL)
	{
		DefaultPawnClass = PlayerPawnBPClass.Class;
	}
}

void AEmberFlightGameMode::BeginPlay()
{
    Super::BeginPlay();
    ListAllNiagaraInterfaces();

    // Initialize Wind Field
    WindFieldInstance = NewObject<UWindVectorField>(this);
    if (WindFieldInstance)
    {
        WindFieldInstance->Initialize(30, 30, 30, 100.0f);
    }
}

Console Log Image with registered wind field interface: https://imgur.com/a/16aRI24
Add User parameters → Data Interface → (custom wind field date interface not appearing): https://imgur.com/a/XJSe4K7

Quite a bit late, but hopefully if anyone encounters the same issue: you need to override PostInitProperties from UObject to “tell” unreal that you want to register your class as a custom type.

You can find a full example provided by Epic here: Niagara Example Custom DataInterface | Unreal Engine 5.7 Documentation | Epic Developer Community

And this is one I’m currently working on(more basic than their example):

Header:

#pragma once

#include “CoreMinimal.h”

#include “NiagaraDataInterface.h”

#include “Enums/EPlayableRingWeapon.h”

#include “PlayableRingWeaponNiagaraDataInterface.generated.h”

// Forward declarations

class UPlayableRingWeaponSubsystem;

UCLASS(BlueprintType, EditInlineNew, Category = “Ring | VFX”, meta = (DisplayName = “Ring Weapon Data Interface”))

class FUNGAME_API UPlayableRingWeaponNiagaraDataInterface

: public UNiagaraDataInterface


{

GENERATED_BODY()

public:

// Cached UPlayableRingWeaponSubsystem


const UPlayableRingWeaponSubsystem* CachedPlayableRingWeaponSubsystem;

// The type of weapon this UNiagaraDataInterface is for


EPlayableRingWeapon Type;

// 


uint32 IndexOffset = 0;

// How many weapons of this Type


uint32 Count = 0;

public:

UPlayableRingWeaponNiagaraDataInterface();

public:

// UObject implementation


void PostInitProperties() override;

// UNiagaraDataInterface implementation


void GetFunctions(TArray& OutFunctions) override;

void GetVMExternalFunction(

const FVMExternalFunctionBindingInfo& BindingInfo,

void* InstanceData,

FVMExternalFunction &OutFunc

) override;


bool Equals(const UNiagaraDataInterface* Other) const override;

bool CopyToInternal(UNiagaraDataInterface* Destination) const override;

// Called GetLocation function


void GetLocation(FVectorVMExternalFunctionContext& Context);

// Called GetRotation function


void GetRotation(FVectorVMExternalFunctionContext& Context);

private:

// Static name for the GetLocation editor function


static const FName GetLocationFnName;

// Static name for the GetRotation editor function


static const FName GetRotationFnName;

private:

// Helper to define GetLocation


void DefineGetLocation(TArray& OutFunctions);

// Helper to define GetRotation


void DefineGetRotation(TArray& OutFunctions);

};

Translation Unit:

#include “VFX/PlayableRingWeaponNiagaraDataInterface.h”

#include “NiagaraTypes.h”

#include “Subsystems/PlayableRingWeaponSubsystem.h”

const FName UPlayableRingWeaponNiagaraDataInterface::GetLocationFnName(TEXT(“GetLocation”));

const FName UPlayableRingWeaponNiagaraDataInterface::GetRotationFnName(TEXT(“GetRotation”));

UPlayableRingWeaponNiagaraDataInterface::UPlayableRingWeaponNiagaraDataInterface()

{

// @TODO: Tell niagara it can run on the GPU


}

void UPlayableRingWeaponNiagaraDataInterface::PostInitProperties()

{

Super::PostInitProperties();

if (HasAnyFlags(RF_ClassDefaultObject))

{


ENiagaraTypeRegistryFlags Flags

= ENiagaraTypeRegistryFlags::AllowAnyVariable

| ENiagaraTypeRegistryFlags::AllowParameter;

FNiagaraTypeRegistry::Register(FNiagaraTypeDefinition{ GetClass()}, Flags );

}


}

void UPlayableRingWeaponNiagaraDataInterface::GetFunctions(TArray& OutFunctions)

{

DefineGetLocation(OutFunctions);

DefineGetRotation(OutFunctions);

}

void UPlayableRingWeaponNiagaraDataInterface::GetVMExternalFunction(

const FVMExternalFunctionBindingInfo& BindingInfo,

void* InstanceData,

FVMExternalFunction &OutFunc

)

{

if (BindingInfo.Name == GetLocationFnName)

{


OutFunc = FVMExternalFunction::CreateUObject(this, &UPlayableRingWeaponNiagaraDataInterface::GetLocation);

}


else if (BindingInfo.Name == GetRotationFnName)

{


OutFunc = FVMExternalFunction::CreateUObject(this, &UPlayableRingWeaponNiagaraDataInterface::GetRotation);

}


}

bool UPlayableRingWeaponNiagaraDataInterface::Equals(const UNiagaraDataInterface* Other) const

{

if (!Super::Equals(Other)) return false;

const UPlayableRingWeaponNiagaraDataInterface* OtherTyped = CastChecked(Other);

return

OtherTyped->CachedPlayableRingWeaponSubsystem == CachedPlayableRingWeaponSubsystem &&

OtherTyped->Type == Type;

}

bool UPlayableRingWeaponNiagaraDataInterface::CopyToInternal(UNiagaraDataInterface* Destination) const

{

if (!Super::CopyToInternal(Destination)) return false;

UPlayableRingWeaponNiagaraDataInterface* DestinationTyped = CastChecked(Destination);

DestinationTyped->CachedPlayableRingWeaponSubsystem = CachedPlayableRingWeaponSubsystem;

DestinationTyped->Type = Type;

return true;

}

void UPlayableRingWeaponNiagaraDataInterface::GetLocation(FVectorVMExternalFunctionContext& Context)

{

FNDIInputParam IndexParam{ Context };

FNDIOutputParam OutLocationsParam{ Context };

const TArray& LocationsBuffer = CachedPlayableRingWeaponSubsystem->GetComputedFrameLocations();

const FVector3f* LocationsBufferPtr = LocationsBuffer.GetData();

const int32 MaxIndex = LocationsBuffer.Num();

for (int32 InstanceIndex = 0; InstanceIndex < Context.GetNumInstances(); ++InstanceIndex)

{


const int32 LocalWeaponIndex = IndexParam.GetAndAdvance();

const int32 GlobalWeaponIndex = IndexOffset + LocalWeaponIndex;

if (GlobalWeaponIndex < MaxIndex)

    {


OutLocationsParam.SetAndAdvance(LocationsBufferPtr[GlobalWeaponIndex]);

    }


else

    {


OutLocationsParam.SetAndAdvance(FVector3f::ZeroVector);

    }

}


}

void UPlayableRingWeaponNiagaraDataInterface::GetRotation(FVectorVMExternalFunctionContext& Context)

{

FNDIInputParam IndexParam{ Context };

FNDIOutputParam OutRotationsParam{ Context };

const TArray& RotationsBuffer = CachedPlayableRingWeaponSubsystem->GetComputedFrameRotations();

const FQuat4f* RotationsBufferPtr = RotationsBuffer.GetData();

const int32 MaxIndex = RotationsBuffer.Num();

for (int32 InstanceIndex = 0; InstanceIndex < Context.GetNumInstances(); ++InstanceIndex)

{


const int32 LocalWeaponIndex = IndexParam.GetAndAdvance();

const int32 GlobalWeaponIndex = IndexOffset + LocalWeaponIndex;

if (GlobalWeaponIndex < MaxIndex)

    {


OutRotationsParam.SetAndAdvance(RotationsBufferPtr[GlobalWeaponIndex]);

    }


else

    {


OutRotationsParam.SetAndAdvance(FQuat4f::Identity);

    }

}


}

void UPlayableRingWeaponNiagaraDataInterface::DefineGetLocation(TArray& OutFunctions)

{

FNiagaraFunctionSignature Signature;

Signature.Name = GetLocationFnName;

Signature.bMemberFunction = true;

Signature.bRequiresContext = false;

Signature.AddInput(FNiagaraVariable{ FNiagaraTypeDefinition{ GetClass() }, TEXT(“DataInterface”) });

Signature.AddInput(FNiagaraVariable{ FNiagaraTypeDefinition::GetIntDef(), TEXT(“Index”) });

Signature.AddOutput(FNiagaraVariable{ FNiagaraTypeDefinition::GetVec3Def(), TEXT(“Location”) });

OutFunctions.Add(Signature);

}

void UPlayableRingWeaponNiagaraDataInterface::DefineGetRotation(TArray& OutFunctions)

{

FNiagaraFunctionSignature Signature;

Signature.Name = GetRotationFnName;

Signature.bMemberFunction = true;

Signature.bRequiresContext = false;

Signature.AddInput(FNiagaraVariable{ FNiagaraTypeDefinition{ GetClass() }, TEXT(“DataInterface”) });

Signature.AddInput(FNiagaraVariable{ FNiagaraTypeDefinition::GetIntDef(), TEXT(“Index”) });

Signature.AddOutput(FNiagaraVariable{ FNiagaraTypeDefinition::GetQuatDef(), TEXT(“Rotation”) });

OutFunctions.Add(Signature);

}

Attention: Your Data Interface will appear as the DisplayName, not the class name(I assume not providing a DisplayName will use the class name, but not sure)