Depending on your game/usecase, there are two possible approaches to retrieve the key bound to a specific Input Action from the Enhanced Input System.

  1. Simple approach: If the action is “active” - as in, an input mapping context with it has been added to the EnhancedInputLocalPlayerSubsystem. This method can be used both in Blueprints and C++.
  2. Complex approach: Any situation where the simple approach doesn’t work. This requires C++.

I’ll detail both the simple and complex approaches below. Let’s look at the simple first.

Simple approach

As long as your input action is active (is added to the local player subsystem), we can query the keys directly from the subsystem. In other words, this approach is useful for usecases such as displaying key hints to the player during gameplay, since in that situation you would have a mapping context active with the input action.

This approach returns the correct key when using player mappable keys also.

Blueprints

C++

ULocalPlayer* LocalPlayer = /* Get the LocalPlayer from somewhere */
auto* LocalPlayerSubsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
 
const TArray<FKey>& Keys = LocalPlayerSubsystem->QueryKeysMappedToAction(Action);

If for any reason this doesn’t work, the following more complicated approach is likely to work. Note that the complex example below also has some example code to get the currently active input device, which can be useful in the simple scenario as well.

Complex approach

The approach detailed here works also for situations where the input mapping context containing a given input action is not active.

In order to get the key that’s bound to some particular Input Action, you may need to query three different places:

  1. The Player Mappable Keys, if the input action is set up to allow the player to change the key bindings
  2. The EnhancedInputLocalPlayerSubsystem, for actions which are not player mappable
  3. All Input Mapping Contexts, for actions which are not player mappable, but also not part of any active input mapping context

If you know for sure that for example the keys are always player mappable and you can only do #1, it simplifies the process quite significantly.

However, if you have a mix of player mappable and non-mappable keys you want to find the current key binding for the process is kind of complicated. The code below should help you get started. Read the comments in the code for more details.

The code here also detects the player input device type in order to choose the most appropriate keybinding to return.

//.h file
UCLASS()
class GAMEJAMREWIND_API UKotLocalPlayerInputSubsystem : public ULocalPlayerSubsystem
{
	GENERATED_BODY()
 
public:
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
 
	UFUNCTION(BlueprintCallable, BlueprintPure=false)
	FKey GetKeyForMostRecentDevice(class UInputAction* Action) const;
 
protected:
	bool GetPlayerMappingForRecentDevice(class UInputAction* Action, FPlayerKeyMapping& OutMapping) const;
	
	FKey GetBestMatchForDevice(const TArray<FKey>& Keys, const FHardwareDeviceIdentifier& Device) const;
 
	UPROPERTY(Transient)
	TArray<UInputMappingContext*> InputMappingContexts;
};
//.cpp file
void UKotLocalPlayerInputSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
 
	//To support usecase #3 where the action is not in any active mapping,
	//we need to have a list of all input mapping contexts that could
	//contain the action. For this purpose, we can query the asset registry.
	const IAssetRegistry& Registry = IAssetRegistry::GetChecked();
 
	//This pulls all the input mapping contexts from the asset registry
	FARFilter Filter;
	Filter.ClassPaths.Emplace(UInputMappingContext::StaticClass()->GetClassPathName());
	TArray<FAssetData> OutAssets;
	Registry.GetAssets(Filter, OutAssets);
	InputMappingContexts.Reserve(OutAssets.Num());
 
	auto* LocalPlayerSubsystem = GetLocalPlayerChecked()->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
	UEnhancedInputUserSettings* UserSettings = LocalPlayerSubsystem->GetUserSettings();
	//Iterate all the assets we got from the registry...
	for(const FAssetData& AssetData : OutAssets)
	{
		auto* Context = CastChecked<UInputMappingContext>(AssetData.GetAsset());
		//Store the contexts into the input mapping contexts array do we can
		//use them later when looking up keys
		InputMappingContexts.Emplace(Context);
 
		//Also register the context with the user settings, so that it has
		//all the player mappable keys available to it
		UserSettings->RegisterInputMappingContext(Context);
	}
}
 
bool UKotLocalPlayerInputSubsystem::GetPlayerMappingForRecentDevice(UInputAction* Action, FPlayerKeyMapping& OutMapping) const
{
	OutMapping = FPlayerKeyMapping();
	
	//First identify which was the most recently used input device
	auto* InputDeviceSubsystem = GetWorld()->GetGameInstance()->GetEngine()->GetEngineSubsystem<UInputDeviceSubsystem>();
	ULocalPlayer* LocalPlayer = GetLocalPlayerChecked();
	FHardwareDeviceIdentifier Device = InputDeviceSubsystem->GetMostRecentlyUsedHardwareDevice(LocalPlayer->GetPlatformUserId());
 
	//Then determine which of the player mappings matches the given input action
	auto* LocalPlayerSubsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
	UEnhancedInputUserSettings* UserSettings = LocalPlayerSubsystem->GetUserSettings();
	if(!ensure(UserSettings))
	{
		return false;
	}
	UEnhancedPlayerMappableKeyProfile* KeyProfile = UserSettings->GetCurrentKeyProfile();
	const TMap<FName, FKeyMappingRow>& Rows = KeyProfile->GetPlayerMappingRows();
	
	const TObjectPtr<UPlayerMappableKeySettings>& MappableSettings = Action->GetPlayerMappableKeySettings();
	//player mappable key setting doesn't exist on input actions which don't have a player mappable key set up
	if(!MappableSettings)
	{
		return false;
	}
 
	const FKeyMappingRow* Row = Rows.Find(MappableSettings->GetMappingName());
	if(!ensure(Row))
	{
		return false;
	}
 
	bool bFallbackSet = false;
	//Find which mapped key matches the most recently used device
	for(const FPlayerKeyMapping& KeyMapping : Row->Mappings)
	{
		if(!bFallbackSet)
		{
			bFallbackSet = true;
			OutMapping = KeyMapping;
		}
 
		if(KeyMapping.GetPrimaryDeviceType() == Device.PrimaryDeviceType)
		{
			OutMapping = KeyMapping;
		}
	}
 
	return true;
}
 
FKey UKotLocalPlayerInputSubsystem::GetKeyForMostRecentDevice(UInputAction* Action) const
{
	//If a player mappable key exists, we want to choose that first
	if(FPlayerKeyMapping Mapping; GetPlayerMappingForRecentDevice(Action, Mapping))
	{
		return Mapping.GetCurrentKey();
	}
 
	ULocalPlayer* LocalPlayer = GetLocalPlayerChecked();
	auto* InputDeviceSubsystem = GetWorld()->GetGameInstance()->GetEngine()->GetEngineSubsystem<UInputDeviceSubsystem>();
	FHardwareDeviceIdentifier Device = InputDeviceSubsystem->GetMostRecentlyUsedHardwareDevice(LocalPlayer->GetPlatformUserId());
	auto* LocalPlayerSubsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
	
	//No player mapping exists for this, get whatever is the default mapping for it from the input mappings
	if(const TArray<FKey>& Keys = LocalPlayerSubsystem->QueryKeysMappedToAction(Action); Keys.Num() > 0)
	{
		return GetBestMatchForDevice(Keys, Device);
	}
 
	//The subsystem only contains key mappings for *active* input mapping contexts. It's possible the action we want
	//is currently not active, so we need to also iterate through all possible input mapping contexts to find out.
	TArray<FKey> FoundKeys;
	for(const UInputMappingContext* MappingContext : InputMappingContexts)
	{
		for(const FEnhancedActionKeyMapping& Mapping : MappingContext->GetMappings())
		{
			if(Mapping.Action == Action)
			{
				FoundKeys.Emplace(Mapping.Key);
			}
		}
	}
	
	return GetBestMatchForDevice(FoundKeys, Device);
}
 
FKey UKotLocalPlayerInputSubsystem::GetBestMatchForDevice(const TArray<FKey>& Keys, const FHardwareDeviceIdentifier& Device) const
{
	FKey BestMatch = EKeys::AnyKey;
	for(const FKey& Key : Keys)
	{
		if(BestMatch == EKeys::AnyKey)
		{
			BestMatch = Key;
		}
 
		if(Device.PrimaryDeviceType == EHardwareDevicePrimaryType::Gamepad && Key.IsGamepadKey())
		{
			BestMatch = Key;
			break;
		}
 
		if(Device.PrimaryDeviceType == EHardwareDevicePrimaryType::KeyboardAndMouse && !Key.IsGamepadKey())
		{
			BestMatch = Key;
			break;
		}
	}
 
	return BestMatch;
}

Note that this is a Local Player Subsystem. This is so that we get easy access to the EnhancedInputLocalPlayerSubsystem, as we need the information in it when querying for the keys.