diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 4667fdbf4a7549ae226075ff8d5f09feeb9cc05f..1324322a286649c0a937dd9251dfc6a3aef1eb49 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -184,12 +184,15 @@ static const struct {
 	{META_CVAR,         "consvar_t"},
 
 	{META_SECTORLINES,  "sector_t.lines"},
+	{META_SECTORTAGLIST, "sector_t.taglist"},
 	{META_SIDENUM,      "line_t.sidenum"},
 	{META_LINEARGS,     "line_t.args"},
 	{META_LINESTRINGARGS, "line_t.stringargs"},
+	{META_LINETAGLIST,  "line_t.taglist"},
 
 	{META_THINGARGS,     "mapthing.args"},
 	{META_THINGSTRINGARGS, "mapthing.stringargs"},
+	{META_THINGTAGLIST,  "mapthing_t.taglist"},
 #ifdef HAVE_LUA_SEGS
 	{META_NODEBBOX,     "node_t.bbox"},
 	{META_NODECHILDREN, "node_t.children"},
diff --git a/src/lua_libs.h b/src/lua_libs.h
index aa0638683cd6de97ead71611c4f458129b019d16..991fae3fd047239d5063f78d742cdb8bd7a4e61a 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -56,11 +56,14 @@ extern lua_State *gL;
 #define META_CVAR "CONSVAR_T*"
 
 #define META_SECTORLINES "SECTOR_T*LINES"
+#define META_SECTORTAGLIST "SECTOR_T*TAGLIST"
 #define META_SIDENUM "LINE_T*SIDENUM"
 #define META_LINEARGS "LINE_T*ARGS"
 #define META_LINESTRINGARGS "LINE_T*STRINGARGS"
+#define META_LINETAGLIST "LINE_T*TAGLIST"
 #define META_THINGARGS "MAPTHING_T*ARGS"
 #define META_THINGSTRINGARGS "MAPTHING_T*STRINGARGS"
+#define META_THINGTAGLIST "THING_T*TAGLIST"
 #define META_POLYOBJVERTICES "POLYOBJ_T*VERTICES"
 #define META_POLYOBJLINES "POLYOBJ_T*LINES"
 #ifdef HAVE_LUA_SEGS
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index a3df28ccac06ededcecde42e09b81f9e6958026b..25edf83d8cb4d53bfe1fd27e8048695b7ba86e84 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -37,6 +37,7 @@ enum sector_e {
 	sector_lightlevel,
 	sector_special,
 	sector_tag,
+	sector_taglist,
 	sector_thinglist,
 	sector_heightsec,
 	sector_camsec,
@@ -55,6 +56,7 @@ static const char *const sector_opt[] = {
 	"lightlevel",
 	"special",
 	"tag",
+	"taglist",
 	"thinglist",
 	"heightsec",
 	"camsec",
@@ -89,6 +91,7 @@ enum line_e {
 	line_flags,
 	line_special,
 	line_tag,
+	line_taglist,
 	line_args,
 	line_stringargs,
 	line_sidenum,
@@ -113,6 +116,7 @@ static const char *const line_opt[] = {
 	"flags",
 	"special",
 	"tag",
+	"taglist",
 	"args",
 	"stringargs",
 	"sidenum",
@@ -581,6 +585,9 @@ static int sector_get(lua_State *L)
 	case sector_tag:
 		lua_pushinteger(L, Tag_FGet(&sector->tags));
 		return 1;
+	case sector_taglist:
+		LUA_PushUserdata(L, &sector->tags, META_SECTORTAGLIST);
+		return 1;
 	case sector_thinglist: // thinglist
 		lua_pushcfunction(L, lib_iterateSectorThinglist);
 		LUA_PushUserdata(L, sector->thinglist, META_MOBJ);
@@ -682,6 +689,8 @@ static int sector_set(lua_State *L)
 	case sector_tag:
 		Tag_SectorFSet((UINT32)(sector - sectors), (INT16)luaL_checkinteger(L, 3));
 		break;
+	case sector_taglist:
+		return LUA_ErrSetDirectly(L, "sector_t", "taglist");
 	}
 	return 0;
 }
@@ -821,6 +830,9 @@ static int line_get(lua_State *L)
 	case line_tag:
 		lua_pushinteger(L, Tag_FGet(&line->tags));
 		return 1;
+	case line_taglist:
+		LUA_PushUserdata(L, &line->tags, META_LINETAGLIST);
+		return 1;
 	case line_args:
 		LUA_PushUserdata(L, line->args, META_LINEARGS);
 		return 1;
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 134f104ee0e7ff9d57e8b666339d044ffd763177..8d205780d05a659d0ce6f5eb9eae7531f4904bea 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -902,6 +902,11 @@ static int mapthing_get(lua_State *L)
 		number = mt->extrainfo;
 	else if(fastcmp(field,"tag"))
 		number = Tag_FGet(&mt->tags);
+	else if(fastcmp(field,"taglist"))
+	{
+		LUA_PushUserdata(L, &mt->tags, META_THINGTAGLIST);
+		return 1;
+	}
 	else if(fastcmp(field,"args"))
 	{
 		LUA_PushUserdata(L, mt->args, META_THINGARGS);
@@ -964,6 +969,8 @@ static int mapthing_set(lua_State *L)
 	}
 	else if (fastcmp(field,"tag"))
 		Tag_FSet(&mt->tags, (INT16)luaL_checkinteger(L, 3));
+	else if (fastcmp(field,"taglist"))
+		return LUA_ErrSetDirectly(L, "mapthing_t", "taglist");
 	else if(fastcmp(field,"mobj"))
 		mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 	else
diff --git a/src/lua_script.h b/src/lua_script.h
index 2dc34446a78841acba3cf20d6982cc4313c00136..77fbb7c1d125f682db270b7d4f051d6673290d43 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -102,6 +102,8 @@ void COM_Lua_f(void);
 
 #define LUA_ErrInvalid(L, type) luaL_error(L, "accessed " type " doesn't exist anymore, please check 'valid' before using " type ".");
 
+#define LUA_ErrSetDirectly(L, type, field) luaL_error(L, type " field " LUA_QL(field) " cannot be set directly.")
+
 // Deprecation warnings
 // Shows once upon use. Then doesn't show again.
 #define LUA_Deprecated(L,this_func,use_instead)\
diff --git a/src/lua_taglib.c b/src/lua_taglib.c
index 2e2a0d277f69cf6256b7ce994e93dcaf57518735..73f0333128a8f35cd483c94843cabca236d2dd65 100644
--- a/src/lua_taglib.c
+++ b/src/lua_taglib.c
@@ -150,6 +150,137 @@ static int lib_numTaggroupElements(lua_State *L)
 	return 1;
 }
 
+static void push_taglist(lua_State *L, int idx)
+{
+	lua_getmetatable(L, idx);
+	lua_pushliteral(L, "taglist");
+	lua_rawget(L, -2);
+	lua_remove(L, -2);
+}
+
+static int has_valid_field(lua_State *L)
+{
+	int equal;
+	lua_pushliteral(L, "valid");
+	equal = lua_rawequal(L, 2, -1);
+	lua_pop(L, 1);
+	return equal;
+}
+
+static taglist_t * valid_taglist(lua_State *L, int idx, boolean getting)
+{
+	taglist_t *list = *(taglist_t **)lua_touserdata(L, idx);
+
+	if (list == NULL)
+	{
+		if (getting && has_valid_field(L))
+			lua_pushboolean(L, 0);
+		else
+			LUA_ErrInvalid(L, "taglist_t");/* doesn't actually return */
+		return NULL;
+	}
+	else
+		return list;
+}
+
+static taglist_t * check_taglist(lua_State *L, int idx)
+{
+	luaL_checktype(L, idx, LUA_TUSERDATA);
+	push_taglist(L, idx);
+	luaL_argcheck(L, lua_toboolean(L, -1), idx, "must be a tag list");
+	return valid_taglist(L, idx, false);
+}
+
+static int taglist_get(lua_State *L)
+{
+	const taglist_t *list = valid_taglist(L, 1, true);
+
+	if (list == NULL)/* valid check */
+		return 1;
+
+	if (lua_isnumber(L, 2))
+	{
+		const size_t i = lua_tonumber(L, 2);
+
+		if (list && i <= list->count)
+		{
+			lua_pushnumber(L, list->tags[i - 1]);
+			return 1;
+		}
+		else
+			return 0;
+	}
+	else if (has_valid_field(L))
+	{
+		lua_pushboolean(L, 1);
+		return 1;
+	}
+	else
+	{
+		push_taglist(L, 1);
+		lua_replace(L, 1);
+		lua_rawget(L, 1);
+		return 1;
+	}
+}
+
+static int taglist_len(lua_State *L)
+{
+	const taglist_t *list = valid_taglist(L, 1, false);
+	lua_pushnumber(L, list->count);
+	return 1;
+}
+
+static int taglist_equal(lua_State *L)
+{
+	const taglist_t *lhs = check_taglist(L, 1);
+	const taglist_t *rhs = check_taglist(L, 2);
+	lua_pushboolean(L, Tag_Compare(lhs, rhs));
+	return 1;
+}
+
+static int taglist_iterator(lua_State *L)
+{
+	const taglist_t *list = valid_taglist(L, 1, false);
+	const size_t i = 1 + lua_tonumber(L, lua_upvalueindex(1));
+	if (i <= list->count)
+	{
+		lua_pushnumber(L, list->tags[i - 1]);
+		/* watch me exploit an upvalue as a control because
+			I want to use the control as the value */
+		lua_pushnumber(L, i);
+		lua_replace(L, lua_upvalueindex(1));
+		return 1;
+	}
+	else
+		return 0;
+}
+
+static int taglist_iterate(lua_State *L)
+{
+	check_taglist(L, 1);
+	lua_pushnumber(L, 0);
+	lua_pushcclosure(L, taglist_iterator, 1);
+	lua_pushvalue(L, 1);
+	return 2;
+}
+
+static int taglist_find(lua_State *L)
+{
+	const taglist_t *list = check_taglist(L, 1);
+	const mtag_t tag = luaL_checknumber(L, 2);
+	lua_pushboolean(L, Tag_Find(list, tag));
+	return 1;
+}
+
+static int taglist_shares(lua_State *L)
+{
+	const taglist_t *lhs = check_taglist(L, 1);
+	const taglist_t *rhs = check_taglist(L, 2);
+	lua_pushboolean(L, Tag_Share(lhs, rhs));
+	return 1;
+}
+
 void LUA_InsertTaggroupIterator
 (		lua_State *L,
 		taggroup_t *garray[],
@@ -179,6 +310,13 @@ void LUA_InsertTaggroupIterator
 	lua_setfield(L, -2, "tagged");
 }
 
+static luaL_Reg taglist_lib[] = {
+	{"iterate", taglist_iterate},
+	{"find", taglist_find},
+	{"shares", taglist_shares},
+	{0}
+};
+
 int LUA_TagLib(lua_State *L)
 {
 	lua_newuserdata(L, 0);
@@ -193,5 +331,23 @@ int LUA_TagLib(lua_State *L)
 		lua_setmetatable(L, -2);
 	lua_setglobal(L, "tags");
 
+	luaL_newmetatable(L, META_THINGTAGLIST);
+		luaL_register(L, "taglist", taglist_lib);
+			lua_getfield(L, -1, "find");
+			lua_setfield(L, -2, "has");
+		lua_setfield(L, -2, "taglist");
+
+		lua_pushcfunction(L, taglist_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, taglist_len);
+		lua_setfield(L, -2, "__len");
+
+		lua_pushcfunction(L, taglist_equal);
+		lua_setfield(L, -2, "__eq");
+	lua_pushvalue(L, -1);
+	lua_setfield(L, LUA_REGISTRYINDEX, META_LINETAGLIST);
+	lua_setfield(L, LUA_REGISTRYINDEX, META_SECTORTAGLIST);
+
 	return 0;
 }