diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index 593e0c4382251df0185c4f23ae990548307e7efa..01763fd73e93a174adfa8088ee675cf7ff5068ad 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -2438,6 +2438,10 @@ These functions return the leftover itemstack.
     * See documentation on `minetest.compress()` for supported compression methods.
     * currently supported.
     * `...` indicates method-specific arguments. Currently, no methods use this.
+* `minetest.encode_base64(string)`: returns string encoded in base64
+    * Encodes a string in base64.
+* `minetest.decode_base64(string)`: returns string
+    * Decodes a string encoded in base64.
 * `minetest.is_protected(pos, name)`: returns boolean
     * Returns true, if player `name` shouldn't be abled to dig at `pos` or do other
       actions, defineable by mods, due to some mod-defined ownership-like concept.
diff --git a/doc/menu_lua_api.txt b/doc/menu_lua_api.txt
index ac8713a32fc396ab7f3c548daa4c01eb18a12a7b..479880266fca9144f3bad955d07786e5d41256cd 100644
--- a/doc/menu_lua_api.txt
+++ b/doc/menu_lua_api.txt
@@ -210,6 +210,10 @@ string:trim()
 ^ eg. string.trim("\n \t\tfoo bar\t ") == "foo bar"
 core.is_yes(arg) (possible in async calls)
 ^ returns whether arg can be interpreted as yes
+minetest.encode_base64(string) (possible in async calls)
+^ Encodes a string in base64.
+minetest.decode_base64(string) (possible in async calls)
+^ Decodes a string encoded in base64.
 
 Version compat:
 core.get_min_supp_proto()
diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp
index c3e6c896445a62bf183c5b1e1e6a189b2009efa0..e90b7fbcf4d909774ec8b0f76676d048aa8fb01c 100644
--- a/src/script/lua_api/l_util.cpp
+++ b/src/script/lua_api/l_util.cpp
@@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "filesys.h"
 #include "settings.h"
 #include "util/auth.h"
+#include "util/base64.h"
 #include <algorithm>
 
 // log([level,] text)
@@ -320,6 +321,34 @@ int ModApiUtil::l_decompress(lua_State *L)
 	return 1;
 }
 
+// encode_base64(string)
+int ModApiUtil::l_encode_base64(lua_State *L)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	size_t size;
+	const char *data = luaL_checklstring(L, 1, &size);
+
+	std::string out = base64_encode((const unsigned char *)(data), size);
+
+	lua_pushlstring(L, out.data(), out.size());
+	return 1;
+}
+
+// decode_base64(string)
+int ModApiUtil::l_decode_base64(lua_State *L)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	size_t size;
+	const char *data = luaL_checklstring(L, 1, &size);
+
+	std::string out = base64_decode(std::string(data, size));
+
+	lua_pushlstring(L, out.data(), out.size());
+	return 1;
+}
+
 // mkdir(path)
 int ModApiUtil::l_mkdir(lua_State *L)
 {
@@ -433,6 +462,9 @@ void ModApiUtil::Initialize(lua_State *L, int top)
 	API_FCT(get_dir_list);
 
 	API_FCT(request_insecure_environment);
+
+	API_FCT(encode_base64);
+	API_FCT(decode_base64);
 }
 
 void ModApiUtil::InitializeAsync(AsyncEngine& engine)
@@ -459,5 +491,8 @@ void ModApiUtil::InitializeAsync(AsyncEngine& engine)
 
 	ASYNC_API_FCT(mkdir);
 	ASYNC_API_FCT(get_dir_list);
+
+	ASYNC_API_FCT(encode_base64);
+	ASYNC_API_FCT(decode_base64);
 }
 
diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h
index 6fac7e7eb67608014e044b66ea949b5bb8dcc79d..779dbe281f7543d9908524db5362be1c4923c009 100644
--- a/src/script/lua_api/l_util.h
+++ b/src/script/lua_api/l_util.h
@@ -95,6 +95,12 @@ class ModApiUtil : public ModApiBase {
 	// request_insecure_environment()
 	static int l_request_insecure_environment(lua_State *L);
 
+	// encode_base64(string)
+	static int l_encode_base64(lua_State *L);
+
+	// decode_base64(string)
+	static int l_decode_base64(lua_State *L);
+
 public:
 	static void Initialize(lua_State *L, int top);