diff --git a/forge-script-lang/src/parser/ast.rs b/forge-script-lang/src/parser/ast.rs
index 460fc416539abae3deb2b68d7812f2247fd3bec6..644913e996c8313a252775e7ea5a9a1d62cf6383 100644
--- a/forge-script-lang/src/parser/ast.rs
+++ b/forge-script-lang/src/parser/ast.rs
@@ -168,6 +168,7 @@ pub enum ValueExpression {
 	ConditionalBlock(Conditional),
 	Identifier(Identifier),
 	FunctionCall(FunctionCall),
+	DeclareFunction(DeclareFunction),
 }
 
 #[derive(Clone)]
@@ -344,6 +345,15 @@ pub enum DeclareIdent {
 	WithoutValue(Identifier),
 }
 
+#[derive(Clone)]
+#[cfg_attr(feature = "debug-ast", derive(Debug))]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+pub struct DeclareFunction {
+	pub ident: Identifier,
+	pub params: Vec<DeclareIdent>,
+	pub body: ExpressionList,
+}
+
 #[derive(Clone)]
 #[cfg_attr(feature = "debug-ast", derive(Debug))]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
diff --git a/forge-script-lang/src/parser/grammar.rs b/forge-script-lang/src/parser/grammar.rs
index 3d8117f7f1b5a673246efc6b14d6ee4999886f34..59fba41f3ac105314207c34d4c45a098d1e7baaf 100644
--- a/forge-script-lang/src/parser/grammar.rs
+++ b/forge-script-lang/src/parser/grammar.rs
@@ -33,6 +33,7 @@ peg::parser! {
 			= "(" ex:value_expression() ")" { ValueExpression::Grouped(GroupedExpression { inner: Box::new(ex) }) }
 			/ co:conditional() { ValueExpression::ConditionalBlock(co) }
 			/ decl:declare_variable() { ValueExpression::DeclareIdentifier(decl) }
+			/ decl:declare_function() { ValueExpression::DeclareFunction(decl) }
 			/ name:bare_identifier() "(" params:param_list()? ")"
 				{ ValueExpression::FunctionCall(FunctionCall { name, params: params.unwrap_or_default() }) }
 			/ left:value_expression() op:binary_operator() right:value_expression()
@@ -46,8 +47,15 @@ peg::parser! {
 			= "print" ex:value_expression() { ex.into() }
 
 		rule declare_variable() -> DeclareIdent
-			= "let" assign:assignment() { DeclareIdent::WithValue(assign) }
-			/ "let" ident:bare_identifier() { DeclareIdent::WithoutValue(ident) }
+			= "let" ex:declare_identifier() { ex }
+
+		rule declare_identifier() -> DeclareIdent
+			= assign:assignment() { DeclareIdent::WithValue(assign) }
+			/ ident:bare_identifier() { DeclareIdent::WithoutValue(ident) }
+
+		rule declare_function() -> DeclareFunction
+			= "fn" ident:bare_identifier() "(" params:(declare_identifier() ** ",") ")" "{" block:expression_list()? "}"
+				{ DeclareFunction { ident, params, body: block.unwrap_or_default() } }
 
 		rule condition_loop() -> ConditionalLoop
 			= "while" guard:value_expression() "{" block:expression_list()? "}"
diff --git a/forge-script-lang/src/parser/test_suite.rs b/forge-script-lang/src/parser/test_suite.rs
index eaec0bc7daa1fbed20bc338e7f87dc6102d2272c..0b00afaa84561bcb6b63bf829e0c3d2cb9d3f546 100644
--- a/forge-script-lang/src/parser/test_suite.rs
+++ b/forge-script-lang/src/parser/test_suite.rs
@@ -58,3 +58,14 @@ fn function_calls() {
 	parse_program("some_func(250, if true { \"some val\" } else { \"123\" })")
 		.expect("Failed complex function params");
 }
+
+#[test]
+fn declare_function() {
+	parse_program("fn some_func() {}").expect("Failed basic function");
+	parse_program("fn some_func(my_param) {}").expect("Failed basic function");
+	parse_program("fn some_func(my_param, second) {}").expect("Failed basic function");
+	parse_program("fn some_func(my_param, second = 1234) {}")
+		.expect("Failed basic function with defaults");
+	parse_program("fn add(first, second) { first + second }")
+		.expect("Failed basic function with body");
+}
diff --git a/forge-script-lang/src/runtime/executor/printer.rs b/forge-script-lang/src/runtime/executor/printer.rs
index 078f8a29a6ed91405361cbf66d20ed3fc20bd8e3..4f95e61c95b24e80170afa2928bc54f50ccc7102 100644
--- a/forge-script-lang/src/runtime/executor/printer.rs
+++ b/forge-script-lang/src/runtime/executor/printer.rs
@@ -161,6 +161,39 @@ impl Visitor for TreePrinter {
 				self.write(list.as_slice().join(", "));
 				self.write(")");
 			}
+			ValueExpression::DeclareFunction(declare) => {
+				self.write("fn ");
+				self.write(&declare.ident);
+				self.write("(");
+				let list = declare
+					.params
+					.iter()
+					.map(|param| {
+						let mut inner = TreePrinter {
+							indent: self.indent,
+							buffer: String::new(),
+						};
+
+						match param {
+							DeclareIdent::WithValue(assign) => {
+								inner.write(&assign.ident);
+								inner.write(" = ");
+								inner.evaluate_value_expression(assign.value.as_ref());
+							}
+							DeclareIdent::WithoutValue(identifier) => inner.write(identifier),
+						}
+
+						inner.take_value()
+					})
+					.collect::<Vec<String>>();
+
+				self.write(list.as_slice().join(", "));
+				self.writeln(") {");
+				self.increment();
+				self.writeln(self.format_expression_list(&declare.body));
+				self.decrement();
+				self.writeln("}");
+			}
 		}
 	}