Understanding Lua

Place to get help with not working mods / modding interface.
Aethyrium
Manual Inserter
Manual Inserter
Posts: 3
Joined: Mon Feb 09, 2026 8:55 pm
Contact:

Understanding Lua

Post by Aethyrium »

Hello friends!

-- Needless exposition
I am a long (long... long...) time Factorio player, but very new to modding. And by very new, I mean I like just started this week (and this is my first real foray into programming). And that journey started with learning how Lua works. So I ran through the Lua crash course on Codecademy, after which I felt like I had a fair rudimentary understanding of the language - enough that when I looked at other code, I felt confident I roughly (emphasis: "rough" - based on complexity of code) understood what was happening. So today I jumped right in to execute what I felt like a was a fairly basic idea for a mod (modifying a base recipe) just to put skills to the test. Many problems later, and a couple of hours later, my mod functions and does what I want it to do. Yay!

While I'd like to do some expanding to my mod, now that I have a portion of it working, I'd like to understand some things, and improve my coding here so I can establish good habits early. So...

-- My Mod
At the most basic, I wanted to add a teeny bit of complexity while preserving default ratio to the transport belt cost - also I wanted to increase general use of iron sticks. My answer was to add 2 sticks to the transport belt recipe, increase gears to 2, and produce 4 belts instead of two. This is where I landed, and it works - it's tested in game and functions (let's ignore that sticks require Electric Energy Distribution 1... I'll figure out how to fix that later / if I keep working on this particular project).

Code: Select all

data.raw.recipe["transport-belt"].ingredients =
	{
		{type = "item", name = "iron-plate", amount = 1},
		{type = "item", name = "iron-gear-wheel", amount = 2},
		{type = "item", name = "iron-stick", amount = 2},
	}
data.raw.recipe["transport-belt"].results = {{type="item", name="transport-belt", amount=4}}
-- The Questions
Of which, I have three... point five(ish) half a dozen. Specific questions are bolded.

First, is that while trying to figure out how to do this, and troubleshooting the various errors I had, I came across this thread/solution to the problem: viewtopic.php?t=40209

In this example mod, the code they use is very small:

Code: Select all

data.raw.recipe["transport-belt"].ingredients =
{
	{"copper-plate", 1},
	{"iron-gear-wheel", 2}
}
And boy did I try to make that work. I kept getting errors (something something dictionary ROOT.data.raw... I broke quality (actually recycling according to the error) at one point. It was wild). Now, once I found a mod on the portal that did something similar to mine (basic recipe tweaks... which I am thinking can be done multiple ways depending at what stage you do it at, but this seems beyond me at the moment), I saw that they were using the full breakout like I am using in my 'finalized' mod. Which is what I thought to do initially, and had I done, would have saved me like at least an hour - but as this solution said solved, I really tried to make it work for me.

Now my best guess here is that this is just old language that doesn't work anymore, which clicked once I got mine working. So my guess here is that the code from the linked thread doesn't work, because the way the code works in 2.0 is just not compatible with that formatting anymore. Can I get confirmation that this is the case, or otherwise, an explanation as to why that exact coding does not seem to work anymore (I couldn't make it work, even with direct copy)?

Question two is a little more in the weeds. In the code that functions there is this line:

Code: Select all

data.raw.recipe["transport-belt"].results = {{type="item", name="transport-belt", amount=4}}
If I were to break this down with my rudimentary understanding of Lua (please forgive any verbiage errors).
  • data.raw is calling the "master table" of information compiled by the game.
  • .recipe is designating a subsection of that.
  • ["transport-belt"] is selecting the object from that subsection that I want to mess with.
  • '.results =' is changing the variable that is the results of that object.
  • {{type="item", name="transport-belt", amount="4""}} are defining what you get.
Assuming my understanding is correct (and please correct me if I'm wrong!) my question (and bonus questions) here is: What is the purpose of the double {{}} around the last part, defining what you get? I (think I) understand why the first set of brackets is needed, we're creating a table to be referenced. But what is the second set of brackets actually doing, it isn't (as far as I can tell) attached to anything else?

Bonus questions about this here are that on the data.raw page of the wiki, it suggests that the '.recipe' potion of this code could be replaced with ["recipe"] in the same way we call the transport belt. So it'd look like this:

Code: Select all

data.raw.["recipe"]["transport-belt"].results = ...
Is this accurate? And if it is accurate, is there a preferred choice between which method is used, or if both, when is "generally considered" appropriate to use one over the other? Additionally, does that mean we couple replace '.results' with ["results"]? If not, can you explain why? And if that's the case, could you replace ["transport-belt"] with .transport-belt, and if not, why not?

Actually, on that note, why does '.results' work at all? Based on my understanding 'results =' is a variable we've defined <somewhere>. In this case in the recipe. I am guessing based on context that the 'thing.thing.thing.thing' format is a way for the code to read a table (which was present in my little Lua course, I have just since done some extra reading and am vaguely familiar) and that the way that data.raw works (basically) is that it compiles things into a table that can be read by the code in a file-like structure so it's basically the same thing as data/raw/recipes/transport-belt/results and the reason we're able to call it is because of tabling much earlier on... If this is way off base and too much to explain to a novice, I understand.

And the final question which isn't really about the mod or Lua specifically, is just about formatting conventions - which the internet seems to have mixed opinions on. How many spaces before a new line of code 2-3-4 (vs. tab in note++ which is what I have been using, and appears equal to 4). Nesting vs. not nesting? Are there objective formatting conventions I should be aware of out of the gate that aren't just "your" personal preference, or is this mostly a personal preference sort of thing?

-- Outro
That... Ended up being a lot. Sorry about that! Just a quick preemptive thank you to anyone who reads through this. And an extra thanks to anyone who can shed light on one or more of these questions for me, I really appreciate your time and assistance!
eugenekay
Filter Inserter
Filter Inserter
Posts: 997
Joined: Tue May 15, 2018 2:14 am
Contact:

Re: Understanding Lua

Post by eugenekay »

Aethyrium wrote: Mon Feb 09, 2026 10:19 pmCan I get confirmation that this is the case, or otherwise, an explanation as to why that exact coding does not seem to work anymore (I couldn't make it work, even with direct copy)?
Yes, version 2.0 changed how recipe ingredients are specified. There used to be a shorthand format for compatibility. The Quality mod chokes if a Recipe does not have a valid Ingredients because it auto-generates recipes for each quality level… it is generally easiest/faster to disable the DLC mods when experimenting with recipe changes unless they require a Space Age item - or use Logic to detect if they are enabled and conditionally add support.
Question two is a little more in the weeds. In the code that functions there is this line:

Code: Select all

data.raw.recipe["transport-belt"].results = {{type="item", name="transport-belt", amount=4}}
If I were to break this down with my rudimentary understanding of Lua (please forgive any verbiage errors).
  • data.raw is calling the "master table" of information compiled by the game.
  • .recipe is designating a subsection of that.
  • ["transport-belt"] is selecting the object from that subsection that I want to mess with.
  • '.results =' is changing the variable that is the results of that object.
  • {{type="item", name="transport-belt", amount="4""}} are defining what you get.
Assuming my understanding is correct (and please correct me if I'm wrong!) my question (and bonus questions) here is: What is the purpose of the double {{}} around the last part, defining what you get? I (think I) understand why the first set of brackets is needed, we're creating a table to be referenced. But what is the second set of brackets actually doing, it isn't (as far as I can tell) attached to anything else?
The outer brackets are defining a ProductPrototype; the inner brackets define a (or multiple) Item/FluidProductPrototype within that wrapper.
Bonus questions about this here are that on the data.raw page of the wiki, it suggests that the '.recipe' potion of this code could be replaced with ["recipe"] in the same way we call the transport belt. So it'd look like this:

Code: Select all

data.raw.["recipe"]["transport-belt"].results = ...
Is this accurate? And if it is accurate, is there a preferred choice between which method is used, or if both, when is "generally considered" appropriate to use one over the other? Additionally, does that mean we couple replace '.results' with ["results"]? If not, can you explain why? And if that's the case, could you replace ["transport-belt"] with .transport-belt, and if not, why not?

Actually, on that note, why does '.results' work at all? Based on my understanding 'results =' is a variable we've defined <somewhere>. In this case in the recipe. I am guessing based on context that the 'thing.thing.thing.thing' format is a way for the code to read a table (which was present in my little Lua course, I have just since done some extra reading and am vaguely familiar) and that the way that data.raw works (basically) is that it compiles things into a table that can be read by the code in a file-like structure so it's basically the same thing as data/raw/recipes/transport-belt/results and the reason we're able to call it is because of tabling much earlier on... If this is way off base and too much to explain to a novice, I understand.
Named properties (like “results”) can be accessed as either a .method; or as a a ["property"]. This is just Lua syntax being weird because it’s an interpreted language. I think Members of an array can only be accessed by name, eg ["transport-belt"]
And the final question which isn't really about the mod or Lua specifically, is just about formatting conventions - which the internet seems to have mixed opinions on. How many spaces before a new line of code 2-3-4 (vs. tab in note++ which is what I have been using, and appears equal to 4). Nesting vs. not nesting? Are there objective formatting conventions I should be aware of out of the gate that aren't just "your" personal preference, or is this mostly a personal preference sort of thing?
There is no single authority on spaces-per-indent-level in Lua. I like to use Tab characters, which are normally 8 characters wide.

Good Luck!
Aethyrium
Manual Inserter
Manual Inserter
Posts: 3
Joined: Mon Feb 09, 2026 8:55 pm
Contact:

Re: Understanding Lua

Post by Aethyrium »

Yes, version 2.0 changed how recipe ingredients are specified. There used to be a shorthand format for compatibility. The Quality mod chokes if a Recipe does not have a valid Ingredients because it auto-generates recipes for each quality level… it is generally easiest/faster to disable the DLC mods when experimenting with recipe changes unless they require a Space Age item - or use Logic to detect if they are enabled and conditionally add support.
Oh perfect, I'm glad I was right about this. Good to know disabling mods and what have you, I will keep that in mind. Thank you!
The outer brackets are defining a ProductPrototype; the inner brackets define a (or multiple) Item/FluidProductPrototype within that wrapper.
I think I'm following this. As I look at the code again, I'm recognizing that this (seems) to functionally be happening on the ingredient line as well.

Code: Select all

	{
		{type = "item", name = "iron-plate", amount = 1},
		{type = "item", name = "iron-gear-wheel", amount = 2}, -- input increased from 1 to 2
		{type = "item", name = "iron-stick", amount = 2}, -- input added
	}
Could also be written

Code: Select all

	{{type = "item", name = "iron-plate", amount = 1}, {type = "item", name = "iron-gear-wheel", amount = 2}, {type = "item", name = "iron-stick", amount = 2}}
...Of course this second writing also asks that you not have eyes, and I suspect is the sort of thing that makes anyone who programs regularly want to lynch you. But a little testing seems to suggest that it works just as well in practice. It could be written then also as something like:

Code: Select all

	{{type = "item", name = "iron-plate", amount = 1},
	{type = "item", name = "iron-gear-wheel", amount = 2},
	{type = "item", name = "iron-stick", amount = 2}}
As some kind of middle ground? My point being that these are all "stylistic" choices, rather than "functional" ones, right?

And as I was mucking around in the code, I was just wondering the same thing about some of the code written in the game:

Code: Select all

data.raw.recipe["transport-belt"].results = {{type="item", name="transport-belt", amount=2}}
Has no spaces between type = "item", etc, but it could be written that way to maintain the same formatting as the ingredients above - or vice versa, the ingredients could be written without the spaces. For example, the above can seamlessly be written as:

Code: Select all

data.raw.recipe["transport-belt"].results = {{type = "item", name = "transport-belt", amount = 2}}
Right?
Named properties (like “results”) can be accessed as either a .method; or as a a ["property"]. This is just Lua syntax being weird because it’s an interpreted language. I think Members of an array can only be accessed by name, eg ["transport-belt"]

There is no single authority on spaces-per-indent-level in Lua. I like to use Tab characters, which are normally 8 characters wide.
This is very informative, thank you!
eugenekay
Filter Inserter
Filter Inserter
Posts: 997
Joined: Tue May 15, 2018 2:14 am
Contact:

Re: Understanding Lua

Post by eugenekay »

Aethyrium wrote: Tue Feb 10, 2026 12:18 amAs some kind of middle ground? My point being that these are all "stylistic" choices, rather than "functional" ones, right?
Correct, whitespace is stripped out by the Lua interpreter at runtime outside of "quoted strings". The first format is probably the sanest for readability.
Aethyrium
Manual Inserter
Manual Inserter
Posts: 3
Joined: Mon Feb 09, 2026 8:55 pm
Contact:

Re: Understanding Lua

Post by Aethyrium »

Correct, whitespace is stripped out by the Lua interpreter at runtime outside of "quoted strings". The first format is probably the sanest for readability.
You're the best. Thank you!

Okay so, I return with new things to understand. I have made my mod, and I'm feeling pretty comfortable with what I did. But I hunger for more. I don't think the mod I made was particularly revolutionary (which was the point!) but kicked open the door. I'd like to try my hands at some things a bit more advanced which I think I will almost assuredly eventually have questions about. But in the meantime, can someone explain the data lifecycle to me like I'm 5?

I have read: https://lua-api.factorio.com/latest/aux ... cycle.html

And I think I understand the gist of it. But not... How or why to make use of that information? I've seen some general information that can be summarized as "create stuff as early in the lifecycle as possible", which makes me wonder... Why ever use anything other than 'data'? While making my mod, one of the things I haven't gotten to work is adding the sounds for new items, which caused crashes. I am not entirely sure why yet, but I have assumed that this has to do with load order and the mod trying to call sounds which aren't loaded yet... So that's a thing to figure out. So my question isn't really about that specifically it's about the three stages more broadly.

What goes where? Why use 'data.updates'? Or maybe not why but when should you? Same with 'data.final.fixes'. What should you edit in 'data' vs. 'data-updates' vs 'data.final.fixes'?

I think it's a three stage structure that's throwing me for a loop. Based on the naming and how distinct the stages are, I can see 'data.final.fixes' as some sort of "execute these these functions once everything else is sorted out and polish it off". This seems like if you wanted to "hard" override something, you'd do it here. But then why is there the middle stage at all?

Bonus question, when is it correct (or maybe when is it NOT correct) to use 'data:extend'? At first I didn't even realize this was ever necessary, as I don't have it in my recipe adjustments for the mod that is definitely working. But I do have it in the added intermediates code, and I seem to recall (though I've processed so much information in two days that maybe I'm misremembering) that when I initially didn't have it, I ran into issues. So what defines when this is or isn't needed? It seems like, omnipresent when I look at other mods, but... The difference there (as I type it) might be that one thing is modifying a thing that already exists, wherein the other is adding a thing that doesn't exist. Is that key?
eugenekay
Filter Inserter
Filter Inserter
Posts: 997
Joined: Tue May 15, 2018 2:14 am
Contact:

Re: Understanding Lua

Post by eugenekay »

Aethyrium wrote: Wed Feb 11, 2026 10:54 pmWhat goes where? Why use 'data.updates'? Or maybe not why but when should you? Same with 'data.final.fixes'. What should you edit in 'data' vs. 'data-updates' vs 'data.final.fixes'?

I think it's a three stage structure that's throwing me for a loop. Based on the naming and how distinct the stages are, I can see 'data.final.fixes' as some sort of "execute these these functions once everything else is sorted out and polish it off". This seems like if you wanted to "hard" override something, you'd do it here. But then why is there the middle stage at all?
Data is for standalone definition, like those provided by the Base Game. This is where you should define “new” prototype data which doesn’t have any dependencies.

Data-Updates allows you to modify the Data loaded by other Mods, such as changing recipes or modifying item stats.

Data-final-fixes is a hackjob that should be avoided if at all possible. Sometimes it is necessary for adding cross-mod compatibility or overriding Data-updates that you don’t want.
Nidan
Filter Inserter
Filter Inserter
Posts: 352
Joined: Sat Nov 21, 2015 1:40 am
Contact:

Re: Understanding Lua

Post by Nidan »

Aethyrium wrote: Mon Feb 09, 2026 10:19 pm Are there objective formatting conventions I should be aware of out of the gate that aren't just "your" personal preference
Even this is a personal recommendation, but if there's one thing: be consistent. Coding style is a topic of endless debate that always boils down to taste. Do whatever feels most reasonable/readable to you and stick to that. When editing other people's code, use the style they've been using.
eugenekay wrote: Mon Feb 09, 2026 11:12 pm Named properties (like “results”) can be accessed as either a .method; or as a a ["property"]. This is just Lua syntax being weird because it’s an interpreted language. I think Members of an array can only be accessed by name, eg ["transport-belt"]
.foo and ["foo"] are mostly equivalent and interchangeable in Lua. ["foo"] always works, the .foo variant is a shorthand (syntactic sugar) to make Lua look more similar to other languages, but only works for names/identifiers (only consisting of letters and digits). E.g. in .transport-belt the - will be treated as minus/subtraction operator. The beginning of chapter 3 of the Lua Reference Manual has more details.

Speaking of the reference manual, if you don't mind some technical reading, read chapter 3. That and chapter 6 is basically all you need when writing Lua. (Of course, add facorios Lua API doc when writing factorio mods.) Ignore chapters 4 and 5, those are the parts the factorio devs need for providing their Lua API.
Aethyrium wrote: Mon Feb 09, 2026 10:19 pm Actually, on that note, why does […] work at all?
In factorios prototype stage, we're just putting tables within table within tables. That a construct like data.raw.recipes["transport-belt"].results = {{type = "item", name = "transport-belt", amount = 4}} happens to have a particular meaning is because the devs decided to expose access to the games data that way. Nothing is stopping us from assigning nonsense like data.raw["the greatest programmer in the world"] = "me", but the game will either ignore it or complain.
Aethyrium wrote: Wed Feb 11, 2026 10:54 pm When is it correct (or maybe when is it NOT correct) to use 'data:extend'?
The official documentation (https://lua-api.factorio.com/latest/typ ... tml#extend , https://lua-api.factorio.com/latest/typ ... ethod.html) is a bit sparse: "It's the primary way to add prototypes to the data table." So, if you're adding new prototypes (items, recipes, technologies, etc.), use data:extend, it probably does some magic aside from sorting things into the correct data.raw.x entry; if you're interacting with an already existing prototype, access it via data.raw.
Aethyrium wrote: Wed Feb 11, 2026 10:54 pm What goes where? Why use 'data.updates'? Or maybe not why but when should you? Same with 'data.final.fixes'. What should you edit in 'data' vs. 'data-updates' vs 'data.final.fixes'?
Try to be done as early as possible (says the one whose mod only has data-final-fixes…); later stages are for compatibility between mods.
Post Reply

Return to “Modding help”