This guide will walk you through the process of creating your first blueprint mod for Hogwarts Legacy. You could consider this to be the “Blueprint 101” course. In time-honoured tradition, this mod will reproduce the blueprint equivalent of the classic “hello world” program.
My plan is for more tutorials to follow this one, each slightly more complex than the previous one. I don't have a specific plan regarding content, but I figure that the easiest way to learn blueprints is to actually play around with them, so we'll just dive right in and see where we end up. I should probably issue a disclaimer at this point: I am not a blueprint expert. In fact I'm a beginner. But as a beginner I'm hopefully well-placed to know what other beginners need to know. These tutorials will basically chart my journey, and perhaps help others who are on the same path. However, if you think you can do a better job (of writing blueprint tutorials) then please do! The more tutorials the better!
In this tutorial I will walk you through the basics of placing and linking the "nodes" that comprise this blueprint mod. In future tutorials I will assume that you've got the hang of that and I'll simply describe how the finished blueprint works.
Although it's possible to create simple blueprints for Hogwarts Legacy using the standard Unreal Engine Editor ("UE4.27" or “UE4” or just “UE"), you will not get very far before you run into problems. Instead, you need a custom version built specifically for Hogwarts Legacy. Therefore, before we start please do the following:
There are six common types of mod for Hogwarts Legacy:
PhoenixShipData.sqlite file. Since the game can only load one copy of this file, mods which make changes to this file will simply over-write each other. The solution to this problem is to merge these “SQL mods” using the Hogwarts Mod Merger program. Anyone interested in this sort of mod should read the guide on this Nexus page. Note that the PhoenixShipData.sqlite file has been updated several times since that page was written, so you should extract more recent copy (using FModel) if you intend to edit it.Blueprint mods can do lots of things. They can be extremely complex or relatively simple. We will start with something relatively simple: for now we will focus on “level blueprints”. These are blueprints which are constructed entirely within a Level Blueprint Map. Don't worry about what that means. All you need to know is that we won't have things like widgets, textures, etc in our mods to begin with. All we will have is events, functions and logic. The whole thing will be enclosed within a single umap file. Again, don't worry if you don't know what that means. It will make more sense once you see it in action.
I recommend constructing the blueprint yourself as we go along, but I'll also upload the finished umap file at the end so you can download it if you get stuck.
The first thing we need to do is plan our mod. What is it going to do and what might we need to achieve that? Well, all it's going to do is display the phrase “Hello World” on the screen when we press a button. So we will need something to look out for a keypress, and then we'll need something to display a message on-screen. I happen to know that in order to display something on screen we need to ask for a couple of things from the game - the UI Manager and the Heads-up Display (HUD). Don't worry about that for now, my point is that we need to perform a few initial tasks before we can display anything. In other words we'll need to initialise the mod.
So that's our plan: 1) initialise the mod, 2) look for a key press, and 3) display the phrase “Hello World". Simple. 😊
Let's go! Open your Phoenix.UProj by double-clicking phoenix.uproject. It should open with an empty level like this:
By default, the Custom Engine is set up with a day-night template. That's what all that stuff in the top right is about:

But we don't want any of that stuff. (Hogwarts Legacy already has it.) If we build a mod with that stuff included we'll get a huge black hole appearing in the sky. So select all of it and hit Delete, then Yes All:

Select Blueprints > Open Level Blueprint

The Level Blueprint will open on the “Event Graph” for your empty level:
Everything a Level Blueprint does must start with an “event” that triggers it to do something. Two examples are provided (as you can see) though they don't currently do anything. We won't need the second one Event Tick so left-click it and hit Delete to delete it. Event BeginPlay executes when the mod is loaded into the game, so this event is perfect for triggering our initialisation.
Before we start, right-click on the background and drag it to the left to give us a bit more space. Blueprints always execute from left-to-right so we'll need some room for more nodes on the right.
We need to create a variable to remember whether or not we've initialised before. In the top left corner of your screen click Add New > Variable.
That will create a new variable which is (by default) a boolean (i.e. it can be true or false). In this case that's exactly what we want so we don't need to change it. We do need to change the name though. There are two places you can do it - either top-left or top right (red arrows). Change either one to Initialized and make sure the box marked Instance Editable (green arrow) is unticked. Making a variable "instance editable" just means that each instance of the Level will have its own copy of that variable. We should only ever have one instance but if a second one is created somehow we want it to share the same value for Initialized. [**Note: I will spell “initialised” the British way (with an “s”) in this tutorial's explanation, but the American way (with a “z”) in the blueprint. Whichever spelling you use in the blueprint, make sure you use that spelling throughout! ]
Drag that variable from the left of the screen and drop it just to the right of the Event BeginPlay node. Once you drop it, it will offer you two options: Get Initialized and Set Initialized. Select Set Initialized:
A new node (called Set) will appear. Select the white terminal or “pin” on the Event BeginPlay node and drag across to the white pin on the Set node. The white pins determine the order in which the nodes are executed. In this case execution begins at the Event BeginPlay node and then proceeds across to the Set node where the variable called Initialized is set to FALSE by default.
In the bottom left of the Set node it will say Initialized that's the name of the variable being set. To the left of that is a red input pin and to the right is a checkbox. The red pin allows you to set the value of the Initialized variable using the output of some other node. The checkbox allows you to set the default value. We will leave it unchecked because we want the blueprint to know that the mod is not initialised at startup. The red pin on the right is to allow you to use the value of the Initialized variable straight away in another node if you need to. Finally, note the message on the right of the screen (red arrow)! We can't set the default value until we “compile” the Level. So go ahead and hit the Compile button.
Before we continue we should probably save our Level Blueprint and maybe give it a proper name. Select the CustomContent folder on the left of the Content Browser. (This is important because the Blueprint Apparate Modloader looks for mods in the CustomContent folder. If you save your mod anywhere else the Modloader won't be able to find it.) Then select File > Save, change Untitled to MyHelloWorld and hit Save. If you now hit the X in the top right corner to close the Level Blueprint window you'll return to your project window and you should see a MyHelloWorld icon in your CustomContent folder. If you check that folder in Windows Explorer you'll see that it is in fact a file called MyHelloWorld.umap. That's the Level Blueprint that we're working on. Select Blueprints > Open Level Blueprint to resume editing the Level Blueprint.
Next we want to actually initialise the mod. We could just add a bunch of nodes to the Event Graph (that's what we're looking at right now) but it's neater to create a custom function to do it. In the top left corner click Add New > Function. Change the name to Initialize and right-click on the background to drag the node over to the left of the screen. We'll need plenty of room. You can also use the mouse roller to make the nodes smaller.
As you can see, our Level Blueprint now has two tabs at the top (marked in red in the image below) but they're both stored within the MyHelloWorld.umap file. Use those tabs to switch back & forth. Our function blueprint has been started off with a purple node called Initialize. That's the name of our custom function. (If our function was called FuzzyDuck that purple node would be called FuzzyDuck.) On the right you can see that our function has no inputs and no outputs. That's fine on this occasion - this function doesn't need any.
The Initialize function will need to get the UI & the HUD, but I'd also like to display some debug information. This will help us to figure out what's going on if there's a problem. So the first thing I want to do is create a Debug variable which will control whether the debug information is displayed. During development and testing I'll have it set to TRUE. Then when I'm ready to release my awesome mod on Nexus I'll set it to FALSE. Like before, create a new boolean variable, call it Debug , untick instance-editable, drag it onto the Level Map, tick the box to set it to TRUE but not the box to set it to TRUE by default, then connect the white execution pin to the Initialize node. You should now have this:
Next we need to get the UI Manager and store a reference (pointer) to it in a variable (for us to use later). To do that we're going to select the white pin on our Set node, drag it to the right and release it. That will bring up a search box so we can search for the node we want to connect to. In that search window type GetUIManager.
As you can see (above), it doesn't find anything. That's because, by default, it performs a “context sensitive” search. Normally that's exactly what you want. But on this occasion there's nothing to indicate that we might be interested in finding a UIManager next, so nothing comes up. On this occasion we need to untick the Context Sensitive checkbox.
Now it finds three matches. The first two are something to do with the map in the game, but the third looks like what we need, so select that.
Okay, so that's not quite what we were expecting. We were expecting the new node to be connected to the previous one via a white execution line. But the new node isn't attached. In fact it doesn't even have any white pins. What's going on? Well, this new node is a function that can be executed at any point. It doesn't depend on anything and it doesn't change anything (that's what the “pure” bit means). It doesn't need to happen during the central chain of execution, so Unreal Engine helps you out by allowing you to put it anywhere. So what do we do with this node? Well, we want to store it's output (the UIManager) in a variable so we can use it later, and the node to Set a variable does need to be in the central chain, so we just need to connect a new Set node to the old Set node, like this:
But hold on a minute. Why is this Set node blue when the other one was red? Well, the clue is in the top right corner. Set nodes are red for Boolean variables but blue for variables of type UIManager. Let's back up and I'll walk you through what I did here. We know we want to store a reference (pointer) to the UIManager so we need to create a variable suitable for storing that. So far we've only created boolean variables but now we need something different. Select Add New > Variable as before, change the name to UIManager and untick the Instance Editable tickbox. At this point it's still red because it's still a boolean, so we need to change that. In the top right corner, select Variable Type and a list of variable types will appear. UIManager isn't listed but a lot of stuff is hidden in the collapsed lists at the bottom. Just go to the top line where it says Search and type UIManager. One match will be found, but when you try to select it four options will appear:
Select the top one. Not only does the description sound like what we want, it's the right colour. That's not as daft as it sounds. If you're ever unable to connect two pins together it'll be because they're not the same type of pin. You can only connect pins of the same type. Typically that means the same colour, so white only connects to white, blue only connect to blue, etc. If you're ever unsure about which variable type you need, check the colour! Now we can drag this variable into our Level Blueprint, select Set, and connect it up to the previous Set and Get UIManage Pure nodes, as shown below.
Next we need to get and store a reference to the game HUD. This is where some trial-and-error is required, because there are several options to choose from. More than one of them can probably be made to work, depending on how they're handled. The one below is what a modder called Darkstar came up with. If anyone finds another way let me know! Note the parameter indicated by the red arrow.
Next I want to display some debug information. If the mod is already initialised I want to say so, and if not I want to say not. So we need the execution to go one way if the Initialized variable is TRUE and a different way if it's FALSE. The node that handles that is called a Branch. To insert a branch just drag off the last white pin in the chain and drop it. In the search window type Branch and select the Branch node.
Once the Branch node is in-place, you can drag backwards off the red pin and type Initialized to find the Initialized variable we set up in the Event Graph. Select Get Initialized.
The Initialized variable will now be used to control which way execution goes after the branch. [**Note: Don't confused the Initialized variable with the Debug variable. They're different! ]
Next I want to display a message on-screen. Hogwarts Legacy has various low-level functions for that, but we'll need to define something that does things the way we want, so let's define a function called DisplayMessage. We want this function to take two input arguments: 1) the message we want to display, which we'll call DebugMessage and 2) a boolean flag which we can switch on & off to enable / disable all the debug messages, which we'll called Enabled. To add these input variables click on the purple DisplayMessage node. That will reveal the properties for that function (on the right). Now hit the + next to Inputs.
Change the new parameter name from NewParam to DebugMessage and change the type from Boolean to String. Hit the + again, change the name from NewParam to Enabled and the type from String back to Boolean. As you can see, the function will gain two new pins, representing these new inputs.
Before we get further into that let's finish off our Initialize function. Click on the Initialize function tab to go back to that, drag off the Branch node's white top execution pin (marked TRUE), drop it and search for the function we just created: DisplayMessage. Select it. A new node will appear. In the little box next to DebugMessage type the message we want to see if execution goes this way: MyHelloWorld Mod is already initialised:
Next do the same thing for the FALSE branch and enter the message MyHelloWorld Mod is now initialised:
Drag the Debug variable into the blueprint, select Get, and connect it up as shown below. And finally, in the case where the mod was not initialised, we want to set the Initialized flag to TRUE. Drag the Initialized variable into the blueprint, select Set, tick the Initialized checkbox, connect it up as shown below, then hit Compile and Save.
That's the Initialize function complete.
We can now go back to the Event Graph tab and complete the Event BeginPlay node chain by adding the Initialize function on the end. I have also annotated the function by adding a comment above it. To do that just hover over a node, when … appears click it, and type your comment. You can also comment a group of nodes by selecting them (use the left mouse button to drag a rectangle around them), then right click and select Create Comment From Selection, as shown below
The result will look something like this:
Let's think about the DisplayMessage function some more. In a lot of UE4 tutorials you'll see the print node used to display information on the screen. Unfortunately we can't use that when modding Hogwarts Legacy. Mods are only accepted by the game if they're Shipping Builds, and the Unreal Engine specifically disables the print node in Shipping Builds. [**Note: It's possible that print could be enabled by changing some settings and rebuilding Phoenix.UProj but I haven't attempted it.] So we must use something else. We could create a widget, but unless you put in a lot of work these always look a bit amateurish. The best solution is to use something that's already built into the game and Darkstar came up with a clever way to use the built-in error display node called Hermes Display Error Message, so we'll use that.
To use the Hermes Display Error Message game function Darkstar figured out that we need to reference the following game assets:
/Game/UI/HUD/Notifications/UI_BP_ErrorMessage/Game/UI/HUD/UI_BP_PhoenixHUDWidget(Don't worry if you're starting to get a bit lost. We'll cover this stuff in more detail in a future tutorial for now just accept that if this works we'll worry about how & why later.)
One quick note - you will often see the folder Game mentioned in connection with Hogwarts Legacy, but you will never see that folder anywhere in the game files. That's because it gets converted into Content somewhere inside Unreal Engine. So whenever you see Game just think of it as Content.
So where can we get these assets from? Well, if you browse through the game files (using FModel or UModel - more on that in Tutorial #102) you will find two assets called:
/Content/UI/HUD/Notifications/UI_BP_ErrorMessage.uasset/Content/UI/HUD/UI_BP_PhoenixHUDWidget.uassetPerfect! So we just export those, put them into the corresponding folder inside our project and cook it right?
WRONG! Sadly that won't work.
The .uasset files inside the game have been cooked in IOSTORE format, which means they've been hashed (scrambled) in a way that can't be un-scrambled. They're no use to us. We can put them in the right place but we''ll just get an error when we try to cook our mod. (I know, because I tried it. The error is: .uasset contains unrecognizable data, check that it is of the expected type.)
Instead of exporting the assets from the game, we have to construct dummy assets to replace them. These are assets that have the same structure as the real asset (and hence the same “pins") but they don't actually do anything. If we're careful not to assign the dummy assets a pakchunk then our mod will get cooked with all the right connections… but the dummy assets won't be included in the mod. Once the mod is injected into the game it'll connect up with the real game assets instead of our dummy ones.
Clever eh? Yes! But also a major PITA!
How we make dummy assets is beyond the scope of this tutorial. We'll cover that in a Tutorial #102.
For now we'll just use some dummy assets that Darkstar made for us.
You can download them here. Copy the
UIfolder from inside that archive to theContentfolder of your project.
Below is a cut-down and slightly amended version of Darkstar's function. See the notes below, then have a go at recreating it. In the case of the three blue nodes with Target on them, use what I've written in the node's comment to find those nodes. Alternatively, you can see if the Editor will supply some of them for you. For example, if you try to connect the Phoenix HUD node to the Cast to UI_BP_PhoenixHUDWidget node, the Editor will magically insert the HudWidget Ref node in the middle for you. It's always worth trying to connect nodes up even when you know they're not compatible, just to see if the Editor will insert some sort of converter node for you. This is particularly useful when you have a String or Text or Name node and the you need to connect it to one of the others – the Editor will convert it for you. Same for Integer and Float numbers. And various other things.
I know what you're thinking: it looks complicated! And it kind-of is. There's a lot going on in this function. But that's because we're using one of the game's built-in functions, so we need to jump through a few hoops to get it to work.
Here's a summary of what this Function is doing:
Enabled input parameter is TRUE. If not then debugging is disabled and no messages should be displayed.TRUE then store the DebugMessage (an input to this Function) in local variable Message. [**Note: This isn't actually necessary, because the Function's inputs are available as variables themselves. But I didn't discover that until later.]HudWidgetRef from the PhoenixHUD, cast it as a UI_BP_PhoenixHUDWidget and then extract the UI BP Error Message and Error Message refs from that. Why? And what does all of that mean? Well, it will perhaps make more sense if we work backwards. We want to use the Function Hermes Display Error Message but that requires a UI_BP_ErrorMessage as input, which we can only get from a UI_BP_PhoenixHUDWidget. But as search in UE Editor reveals that there is no Function to give us one of those. But our dummy asset contains a reference for one. So if we can find a Function that gives us the Parent of the HUDWidget then we can get the HUDWidget Reference from there. Fortunately there is a function to get the parent - it's Get Actor of Class PhoenixHUD, which we already executed in the Initialize Function. So we just need to pick up the PhoenixHUD Variable, extract the HUDWidget Reference, tell the mod that it's a UI_BP_PhoenixHUDWidget and extract the UI_BP_ErrorMessage. Simple! Okay, not simple. But this is the sort of thing we have to do when the game doesn't provide us with a Function to get something. The Hermes Display Error Message node actually requires three inputs: a Target, a Caller, and a String. The String is just the message we want to display, in String format. But to find out what Target and Caller are, hover your cursor over those pins. You'll see that Target is a UI BP Error Message Object Reference while Caller just says Object Reference. It probably took Darkstar quite a while to figure out how to provide the function with the inputs it needs! Fortunately they were kind enough to tell the rest of us how they did it so we owe them a big thank you! It turns out that Caller needs to be the Biped_Player (that's one of the hierarchy of “classes” that describe the player). We don't really need to know why. We just need to know that it works.Text Block variable called Translatable to FALSE at one point, then TRUE again later. This is a work-around for a peculiar feature of Unreal Engine - if you provide text to some UE functions without providing “localisation” support (i.e. translation into other languages) then UE will put square brackets around it like [this]. You may have seen this with some Hogwarts Legacy mods. Darkstar found a way around this by setting the value of the Translatable variable of the Text Block to FALSE just before displaying the message and resetting it afterwards.Reroute Nodes. The easiest way to create a Reroute Node is to double-click any line.Okay, we only have one thing left to do - figure out how to display the phrase “Hello World” when a key is pressed. Fortunately that's actually really easy now that we have all the pieces in place. We just need to set up a "keyboard event" on our Event Graph. I'm going use ALT-NUMPAD5 as my input key, but you can choose whatever you like. Proceed as follows: right click on the background of the Event Graph and search for Keyboard Events. Select the key you want to use.
A new node will appear with the name of your selected key in the title. On the right you will see some options. If you want to use CTRL or ALT or whatever, tick the corresponding box. The name of your node will change accordingly. Next drag off the white execution pin of this node, search for DisplayMessage and select our function. On the new node tick the Enabled input and type Hello World into the DebugMessage input, as below, then annotate as you like:
You may have already realised that there's a mistake in the Event BeginPlay logic - the Initialized flag should only be set inside the Initialize Function, otherwise duplicate instances of the mod (if any are ever created) won't be destroyed. We therefore need to delete that Set node. To do that select it and hit SHIFT-Delete. The SHIFT will result in the white connections being preserved after the node is gone, like this:
That's it! We're done. Now we just have to cook it and see if it works! Proceed as follows:
Compile and Save.X to close your Level Blueprint.MyHelloWorld icon, select Asset Actions > Assign to Chunk and enter 101 as the chunk number.Save Current.File > Package Project > Windows (64-bit).Show Output Log if you like. It will be full of warnings…BUILD SUCCESFUL :
In Windows Explorer go to the output folder then navigate down the folder structure to WindowsNoEditor\Phoenix\Content\Paks. You should see a load of pakchunk files. Ignore all of them except the following:

Select all three and rename them to whatever you want (but it's best if the name starts with a z ends with _P and preserves the pakchunk number:

Steam\steamapps\common\Hogwarts Legacy\Phoenix\Content\Paks\~mods folder.We need to use Blueprint Apparate Mod Loader to load the mod. Hit F8 to reveal the modloader window.

Proceed as follows:
MyHelloWorld (the name of our Level Blueprint) then hit Enter.MyHelloWorld should reappear in the lower part of the window with an x against it. If it doesn't something has gone wrong. Either you put the mod files in the wrong folder (they need to go in ~mods), or you changed the name of the .umap file, or you didn't save the .umap file in the CustomContent folder, or one of your other mods is using pakchunk 101.MyHelloWorld in the Blueprint Apparate window, because you didn't take four attempts to get it right like I did!). Note the message in the middle of the screen! That's the DebugMessage from our Initialize function!F8 to get rid of the Blueprint Apparate window.
Hit your chosen key (in my case ALT-NUMPAD5) and you should see something like the image below. [**Note: For ALT-NUMPAD5 to work, you must have NUMLOCK ON.]
That's the end of this tutorial. I will post further tutorials as they occur to me. In future I'll concentrate more on content and less on the mechanics of drawing the blueprint graphs…
The next Tutorial is already up: Blueprint Example 102 - Dummying Game Assets.
If you had any problems creating the blueprints in this tutorial just download the umap file from here, rename it to
MyHelloWorld.umap(noting the uppercase characters) and use Windows Explorer to drag & drop it into theCustomContentfolder of your PhoenixUProj.
[**Note: Bizarrely there is no way to import .umap files into Unreal Engine. You have to use Windows Explorer! ]
Special thanks must go to Narknon, Tangerie, Dekita and Darkstar who all contributed example blueprints for the Hogwarts Legacy modding community to learn from. Darkstar went above-and-beyond by writing a detailed walk-through of their Lore Friendly Apparition mod for me, which helped tremendously. Special thanks must also go to Narknon without whom a significant fraction of what we can do with Hogwarts Legacy mods would not even be possible.