diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java
index e91b144678f..2f53c4eac38 100644
--- a/src/main/java/ch/njol/skript/Skript.java
+++ b/src/main/java/ch/njol/skript/Skript.java
@@ -99,6 +99,7 @@
 import org.skriptlang.skript.bukkit.input.InputModule;
 import org.skriptlang.skript.bukkit.log.runtime.BukkitRuntimeErrorConsumer;
 import org.skriptlang.skript.bukkit.loottables.LootTableModule;
+import org.skriptlang.skript.bukkit.portal.PortalModule;
 import org.skriptlang.skript.bukkit.registration.BukkitRegistryKeys;
 import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos;
 import org.skriptlang.skript.bukkit.tags.TagModule;
@@ -587,7 +588,7 @@ public void onEnable() {
 			TagModule.load();
 			FurnaceModule.load();
 			LootTableModule.load();
-			skript.loadModules(new DamageSourceModule());
+			skript.loadModules(new DamageSourceModule(), new PortalModule());
 		} catch (final Exception e) {
 			exception(e, "Could not load required .class files: " + e.getLocalizedMessage());
 			setEnabled(false);
diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java
index d7c7f0ccc6f..d2b8781034e 100644
--- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java
+++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java
@@ -1632,5 +1632,12 @@ public String toVariableNameString(WorldBorder border) {
 			.since("2.12")
 		);
 
+		Classes.registerClass(new EnumClassInfo<>(PortalType.class, "portaltype", "portal types")
+			.user("portal ?types?")
+			.name("Portal Type")
+			.description("Represents the type of a portal.")
+			.since("INSERT VERSION")
+		);
+
 	}
 }
diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java
index 81e3ad5762e..a600c2e1b6d 100644
--- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java
+++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java
@@ -510,14 +510,6 @@ else if (hand == EquipmentSlot.OFF_HAND)
 		EventValues.registerEventValue(InventoryPickupItemEvent.class, Inventory.class, InventoryPickupItemEvent::getInventory);
 		EventValues.registerEventValue(InventoryPickupItemEvent.class, Item.class, InventoryPickupItemEvent::getItem);
 		EventValues.registerEventValue(InventoryPickupItemEvent.class, ItemStack.class, event -> event.getItem().getItemStack());
-		//PortalCreateEvent
-		EventValues.registerEventValue(PortalCreateEvent.class, World.class, WorldEvent::getWorld);
-		EventValues.registerEventValue(PortalCreateEvent.class, Block[].class, event -> event.getBlocks().stream()
-			.map(BlockState::getBlock)
-			.toArray(Block[]::new));
-		if (Skript.methodExists(PortalCreateEvent.class, "getEntity")) { // Minecraft 1.14+
-			EventValues.registerEventValue(PortalCreateEvent.class, Entity.class, PortalCreateEvent::getEntity);
-		}
 		//PlayerEditBookEvent
 		EventValues.registerEventValue(PlayerEditBookEvent.class, ItemStack.class, event -> {
 			ItemStack book = new ItemStack(Material.WRITABLE_BOOK);
diff --git a/src/main/java/ch/njol/skript/events/EvtPortal.java b/src/main/java/ch/njol/skript/events/EvtPortal.java
deleted file mode 100644
index 4055e0fd7f5..00000000000
--- a/src/main/java/ch/njol/skript/events/EvtPortal.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package ch.njol.skript.events;
-
-import org.bukkit.event.Event;
-import org.bukkit.event.entity.EntityPortalEvent;
-import org.bukkit.event.player.PlayerPortalEvent;
-import org.jetbrains.annotations.Nullable;
-
-import ch.njol.skript.Skript;
-import ch.njol.skript.lang.Literal;
-import ch.njol.skript.lang.SkriptEvent;
-import ch.njol.skript.lang.SkriptParser.ParseResult;
-import ch.njol.util.coll.CollectionUtils;
-
-public class EvtPortal extends SkriptEvent {
-
-	static {
-		Skript.registerEvent("Portal", EvtPortal.class, CollectionUtils.array(PlayerPortalEvent.class, EntityPortalEvent.class), "[player] portal", "entity portal")
-				.description(
-					"Called when a player or an entity uses a nether or end portal. Note that 'on entity portal' event does not apply to players.",
-					"Cancel the event to prevent the entity from teleporting."
-				).keywords(
-					"player", "entity"
-				).examples(
-					"on portal:",
-						"\tbroadcast \"%player% has entered a portal!\"",
-					"",
-					"on player portal:",
-						"\tplayer's world is world(\"wilderness\")",
-						"\tset world of event-location to player's world",
-						"\tadd 9000 to x-pos of event-location",
-					"",
-					"on entity portal:",
-						"\tbroadcast \"A %type of event-entity% has entered a portal!"
-				).since("1.0, 2.5.3 (entities), INSERT VERSION (location changers)");
-	}
-
-	private boolean isPlayer;
-
-	@Override
-	public boolean init(Literal>[] args, int matchedPattern, ParseResult parseResult) {
-		isPlayer = matchedPattern == 0;
-		return true;
-	}
-
-	@Override
-	public boolean check(Event event) {
-		if (isPlayer)
-			return event instanceof PlayerPortalEvent;
-		return event instanceof EntityPortalEvent;
-	}
-
-	@Override
-	public String toString(@Nullable Event event, boolean debug) {
-		return (isPlayer ? "player" : "entity") + " portal";
-	}
-
-}
diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java
index e06163052c0..cbed282235b 100644
--- a/src/main/java/ch/njol/skript/events/SimpleEvents.java
+++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java
@@ -116,10 +116,6 @@ public class SimpleEvents {
 				.examples("on explosion:")
 				.since("1.0");
 //		Skript.registerEvent(SimpleEvent.class, EntityInteractEvent.class, "interact");// = entity interacts with block, e.g. endermen?; player -> PlayerInteractEvent // likely tripwires, pressure plates, etc.
-		Skript.registerEvent("Portal Enter", SimpleEvent.class, EntityPortalEnterEvent.class, "portal enter[ing]", "entering [a] portal")
-				.description("Called when an entity enters a nether portal or an end portal. Please note that this event will be fired many times for a nether portal.")
-				.examples("on portal enter:")
-				.since("1.0");
 		Skript.registerEvent("Tame", SimpleEvent.class, EntityTameEvent.class, "[entity] tam(e|ing)")
 				.description("Called when a player tames a wolf or ocelot. Can be cancelled to prevent the entity from being tamed.")
 				.examples("on tame:")
@@ -221,12 +217,6 @@ public class SimpleEvents {
 						"	player is not sprinting",
 						"	send \"Run!\"")
 				.since("1.0");
-		Skript.registerEvent("Portal Create", SimpleEvent.class, PortalCreateEvent.class, "portal creat(e|ion)")
-				.description("Called when a portal is created, either by a player or mob lighting an obsidian frame on fire, or by a nether portal creating its teleportation target in the nether/overworld.",
-						"In Minecraft 1.14+, you can use the player in this event.", "Please note that there may not always be a player (or other entity) in this event.")
-				.examples("on portal create:")
-				.requiredPlugins("Minecraft 1.14+ (event-entity support)")
-				.since("1.0, 2.5.3 (event-entity support)");
 		Skript.registerEvent("Projectile Hit", SimpleEvent.class, ProjectileHitEvent.class, "projectile hit")
 				.description("Called when a projectile hits an entity or a block.")
 				.examples("on projectile hit:",
diff --git a/src/main/java/ch/njol/skript/expressions/ExprPortal.java b/src/main/java/ch/njol/skript/expressions/ExprPortal.java
deleted file mode 100644
index a3c3011febe..00000000000
--- a/src/main/java/ch/njol/skript/expressions/ExprPortal.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package ch.njol.skript.expressions;
-
-import java.util.Iterator;
-import java.util.List;
-
-import org.bukkit.block.Block;
-import org.bukkit.block.BlockState;
-import org.bukkit.event.Event;
-import org.bukkit.event.world.PortalCreateEvent;
-import org.jetbrains.annotations.Nullable;
-
-import ch.njol.skript.Skript;
-import ch.njol.skript.doc.Description;
-import ch.njol.skript.doc.Events;
-import ch.njol.skript.doc.Examples;
-import ch.njol.skript.doc.Name;
-import ch.njol.skript.doc.Since;
-import ch.njol.skript.lang.Expression;
-import ch.njol.skript.lang.ExpressionType;
-import ch.njol.skript.lang.SkriptParser.ParseResult;
-import ch.njol.skript.lang.util.SimpleExpression;
-import ch.njol.util.Kleenean;
-
-@Name("Portal")
-@Description("The blocks associated with a portal in the portal creation event.")
-@Examples({"on portal creation:",
-		"	loop portal blocks:",
-		"		broadcast \"%loop-block% is part of a portal!\""})
-@Since("2.4")
-@Events("portal_create")
-public class ExprPortal extends SimpleExpression {
-
-	// 1.14+ returns List, 1.13.2 and below returns ArrayList 
-	private static final boolean USING_BLOCKSTATE = Skript.isRunningMinecraft(1, 14);
-	
-	static {
-		Skript.registerExpression(ExprPortal.class, Block.class, ExpressionType.SIMPLE, 
-				"[the] portal['s] blocks",
-				"[the] blocks of [the] portal");
-	}
-
-	@Override
-	public boolean init(Expression>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) {
-		if (getParser().isCurrentEvent(PortalCreateEvent.class))
-			return true;
-		Skript.error("The 'portal' expression may only be used in a portal creation event.");
-		return false;
-	}
-
-	@Nullable
-	@Override
-	protected Block[] get(Event e) {
-		if (!(e instanceof PortalCreateEvent))
-			return null;
-
-		List> blocks = ((PortalCreateEvent) e).getBlocks();
-		if (USING_BLOCKSTATE)
-			return blocks.stream()
-					.map(block -> ((BlockState) block).getBlock())
-					.toArray(Block[]::new);
-		return blocks.stream()
-				.map(Block.class::cast)
-				.toArray(Block[]::new);
-	}
-
-	@Nullable
-	@Override
-	public Iterator iterator(Event e) {
-		if (!(e instanceof PortalCreateEvent))
-			return null;
-
-		List> blocks = ((PortalCreateEvent) e).getBlocks();
-		if (USING_BLOCKSTATE) 
-			return blocks.stream()
-					.map(block -> ((BlockState) block).getBlock())
-					.iterator();
-		return (Iterator) blocks.iterator();
-	}
-
-	@Override
-	public boolean isSingle() {
-		return false;
-	}
-
-	@Override
-	public boolean isDefault() {
-		return true;
-	}
-
-	@Override
-	public Class getReturnType() {
-		return Block.class;
-	}
-
-	@Override
-	public String toString(@Nullable Event e, boolean debug) {
-		return "the portal blocks";
-	}
-
-}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/portal/PortalModule.java b/src/main/java/org/skriptlang/skript/bukkit/portal/PortalModule.java
new file mode 100644
index 00000000000..07cd0a69124
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/portal/PortalModule.java
@@ -0,0 +1,76 @@
+package org.skriptlang.skript.bukkit.portal;
+
+import org.bukkit.Location;
+import org.bukkit.PortalType;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockState;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.entity.EntityPortalEnterEvent;
+import org.bukkit.event.entity.EntityPortalExitEvent;
+import org.bukkit.event.world.PortalCreateEvent;
+import org.bukkit.util.Vector;
+
+import java.io.IOException;
+
+import ch.njol.skript.Skript;
+import ch.njol.skript.lang.util.SimpleEvent;
+import ch.njol.skript.registrations.EventConverter;
+import ch.njol.skript.registrations.EventValues;
+import org.skriptlang.skript.addon.AddonModule;
+import org.skriptlang.skript.addon.SkriptAddon;
+
+public class PortalModule implements AddonModule {
+
+	@Override
+	public void load(SkriptAddon addon) {
+		try {
+			Skript.getAddonInstance().loadClasses("org.skriptlang.skript.bukkit.portal", "elements");
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+
+		Skript.registerEvent("Portal Create", SimpleEvent.class, PortalCreateEvent.class, "portal creat(e|ion)")
+			.description("Called when a portal is created, either by a player or mob lighting an obsidian frame on fire, or by a nether portal creating its teleportation target in the nether/overworld.",
+				"In Minecraft 1.14+, you can use the player in this event.", "Please note that there may not always be a player (or other entity) in this event.")
+			.examples("on portal create:")
+			.since("1.0, 2.5.3 (event-entity support), INSERT VERSION (event-portaltype support)");
+		EventValues.registerEventValue(PortalCreateEvent.class, Block[].class, event -> event.getBlocks().stream()
+			.map(BlockState::getBlock)
+			.toArray(Block[]::new));
+		EventValues.registerEventValue(PortalCreateEvent.class, Entity.class, PortalCreateEvent::getEntity);
+		EventValues.registerEventValue(PortalCreateEvent.class, PortalType.class, event -> switch (event.getReason()) {
+			case END_PLATFORM -> PortalType.ENDER;
+			case FIRE, NETHER_PAIR -> PortalType.NETHER;
+		});
+
+		Skript.registerEvent("Portal Enter", SimpleEvent.class, EntityPortalEnterEvent.class, "portal enter[ing]", "entering [a] portal")
+			.description("Called when an entity enters a nether portal or an end portal. Please note that this event will be fired many times for a nether portal.")
+			.examples(
+				"on portal enter:",
+					"\tbroadcast \"%event-entity% is entering a %event-portaltype% at %event-location%\"")
+			.since("1.0, INSERT VERSION (event values)");
+		EventValues.registerEventValue(EntityPortalEnterEvent.class, Location.class, EntityPortalEnterEvent::getLocation);
+		EventValues.registerEventValue(EntityPortalEnterEvent.class, PortalType.class, EntityPortalEnterEvent::getPortalType);
+
+		Skript.registerEvent("Portal Exit", SimpleEvent.class, EntityPortalExitEvent.class, "portal exit[ing]", "exiting [a] portal")
+			.description("Called when an entity exits a nether portal or an end portal. Note that this event does not get called on players.")
+			.examples(
+				"on portal exit:",
+					"\tbroadcast \"%event-entity% is exiting a portal at %event-location%\"",
+					"\tadd 2 to vector y of event-vector")
+			.since("INSERT VERSION");
+		EventValues.registerEventValue(EntityPortalExitEvent.class, Vector.class, EntityPortalExitEvent::getBefore, EventValues.TIME_PAST);
+		EventValues.registerEventValue(EntityPortalExitEvent.class, Vector.class, new EventConverter<>() {
+			@Override
+			public void set(EntityPortalExitEvent event, Vector vector) {
+				event.setAfter(vector);
+			}
+
+			@Override
+			public Vector convert(EntityPortalExitEvent event) {
+				return event.getAfter();
+			}
+		});
+	}
+
+}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/portal/elements/EvtPortal.java b/src/main/java/org/skriptlang/skript/bukkit/portal/elements/EvtPortal.java
new file mode 100644
index 00000000000..7a39347342b
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/portal/elements/EvtPortal.java
@@ -0,0 +1,75 @@
+package org.skriptlang.skript.bukkit.portal.elements;
+
+import com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent;
+import com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent;
+import org.bukkit.PortalType;
+import org.bukkit.event.Event;
+import org.bukkit.event.entity.EntityPortalEvent;
+import org.bukkit.event.player.PlayerPortalEvent;
+import org.jetbrains.annotations.Nullable;
+
+import ch.njol.skript.Skript;
+import ch.njol.skript.lang.Literal;
+import ch.njol.skript.lang.SkriptEvent;
+import ch.njol.skript.lang.SkriptParser.ParseResult;
+import ch.njol.skript.registrations.EventValues;
+import ch.njol.util.coll.CollectionUtils;
+
+public class EvtPortal extends SkriptEvent {
+
+	static {
+		Class extends Event>[] events = CollectionUtils.array(
+			PlayerPortalEvent.class, EntityPortalEvent.class, PlayerTeleportEndGatewayEvent.class,
+			EntityTeleportEndGatewayEvent.class
+		);
+		Skript.registerEvent("Portal / End Gateway", EvtPortal.class, events, "[player] portal", "entity portal")
+				.description(
+					"Called when a player or an entity uses a nether portal, end portal or end gateway. Note that events with keyword 'entity' does not apply to players.",
+					"Cancel the event to prevent the entity from teleporting."
+				).keywords(
+					"player", "entity"
+				).examples(
+					"on portal:",
+						"\tbroadcast \"%player% has entered a %event-portaltype%!\"",
+					"",
+					"on player portal:",
+						"\tplayer's world is world(\"wilderness\")",
+						"\tset world of event-location to player's world",
+						"\tadd 9000 to x-pos of event-location",
+					"",
+					"on entity portal:",
+						"\tbroadcast \"A %type of event-entity% has entered a portal!"
+				).since("1.0, 2.5.3 (entities), INSERT VERSION (location changers, end gateway)");
+
+		EventValues.registerEventValue(EntityPortalEvent.class, PortalType.class, EntityPortalEvent::getPortalType);
+		EventValues.registerEventValue(PlayerPortalEvent.class, PortalType.class, event -> switch (event.getCause()) {
+			case END_GATEWAY -> PortalType.END_GATEWAY;
+			case END_PORTAL -> PortalType.ENDER;
+			case NETHER_PORTAL -> PortalType.NETHER;
+			default -> throw new UnsupportedOperationException();
+		});
+		EventValues.registerEventValue(EntityTeleportEndGatewayEvent.class, PortalType.class, event -> PortalType.END_GATEWAY);
+		EventValues.registerEventValue(PlayerTeleportEndGatewayEvent.class, PortalType.class, event -> PortalType.END_GATEWAY);
+	}
+
+	private boolean isPlayer;
+
+	@Override
+	public boolean init(Literal>[] args, int matchedPattern, ParseResult parseResult) {
+		isPlayer = matchedPattern == 0;
+		return true;
+	}
+
+	@Override
+	public boolean check(Event event) {
+		if (isPlayer)
+			return event instanceof PlayerPortalEvent || event instanceof PlayerTeleportEndGatewayEvent;
+		return event instanceof EntityPortalEvent || event instanceof EntityTeleportEndGatewayEvent;
+	}
+
+	@Override
+	public String toString(@Nullable Event event, boolean debug) {
+		return (isPlayer ? "player" : "entity") + " portal";
+	}
+
+}
diff --git a/src/main/java/org/skriptlang/skript/bukkit/portal/elements/ExprPortalBlocks.java b/src/main/java/org/skriptlang/skript/bukkit/portal/elements/ExprPortalBlocks.java
new file mode 100644
index 00000000000..ae5ffda677f
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/bukkit/portal/elements/ExprPortalBlocks.java
@@ -0,0 +1,78 @@
+package org.skriptlang.skript.bukkit.portal.elements;
+
+import java.util.Iterator;
+
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockState;
+import org.bukkit.event.Event;
+import org.bukkit.event.world.PortalCreateEvent;
+import org.jetbrains.annotations.Nullable;
+
+import ch.njol.skript.Skript;
+import ch.njol.skript.doc.*;
+import ch.njol.skript.lang.Expression;
+import ch.njol.skript.lang.ExpressionType;
+import ch.njol.skript.lang.SkriptParser.ParseResult;
+import ch.njol.skript.lang.util.SimpleExpression;
+import ch.njol.util.Kleenean;
+
+@Name("Portal Blocks")
+@Description("The blocks associated with a portal in the portal creation event.")
+@Example("""
+	on portal creation:,
+		loop portal blocks:,
+			broadcast "%loop-block% is part of a portal!"
+""")
+@Since("2.4")
+@Events("portal_create")
+public class ExprPortalBlocks extends SimpleExpression {
+
+	static {
+		Skript.registerExpression(ExprPortalBlocks.class, Block.class, ExpressionType.SIMPLE,
+				"[the] portal['s] blocks",
+				"[the] blocks of [the] portal");
+	}
+
+	@Override
+	public boolean init(Expression>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) {
+		if (getParser().isCurrentEvent(PortalCreateEvent.class))
+			return true;
+		Skript.error("The 'portal' expression may only be used in a portal creation event.");
+		return false;
+	}
+
+	@Override
+	protected Block[] get(Event event) {
+		return ((PortalCreateEvent) event).getBlocks().stream()
+				.map(BlockState::getBlock)
+				.toArray(Block[]::new);
+	}
+
+	@Override
+	public Iterator iterator(Event event) {
+		return ((PortalCreateEvent) event).getBlocks().stream()
+				.map(BlockState::getBlock)
+				.iterator();
+	}
+
+	@Override
+	public boolean isSingle() {
+		return false;
+	}
+
+	@Override
+	public boolean isDefault() {
+		return true;
+	}
+
+	@Override
+	public Class getReturnType() {
+		return Block.class;
+	}
+
+	@Override
+	public String toString(@Nullable Event event, boolean debug) {
+		return "the portal blocks";
+	}
+
+}
diff --git a/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java b/src/main/java/org/skriptlang/skript/bukkit/portal/elements/ExprPortalCooldown.java
similarity index 86%
rename from src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java
rename to src/main/java/org/skriptlang/skript/bukkit/portal/elements/ExprPortalCooldown.java
index 09762d8f437..299fa0f8579 100644
--- a/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java
+++ b/src/main/java/org/skriptlang/skript/bukkit/portal/elements/ExprPortalCooldown.java
@@ -1,10 +1,7 @@
-package ch.njol.skript.expressions;
+package org.skriptlang.skript.bukkit.portal.elements;
 
 import ch.njol.skript.classes.Changer.ChangeMode;
-import ch.njol.skript.doc.Description;
-import ch.njol.skript.doc.Examples;
-import ch.njol.skript.doc.Name;
-import ch.njol.skript.doc.Since;
+import ch.njol.skript.doc.*;
 import ch.njol.skript.expressions.base.SimplePropertyExpression;
 import ch.njol.skript.util.Timespan;
 import ch.njol.util.coll.CollectionUtils;
@@ -20,11 +17,11 @@
 	"Players in survival/adventure get a cooldown of 0.5 seconds, while those in creative get no cooldown.",
 	"Resetting will set the cooldown back to the default 15 seconds for non-player entities and 0.5 seconds for players."
 })
-@Examples({
-	"on portal:",
-		"\twait 1 tick",
-		"\tset portal cooldown of event-entity to 5 seconds"
-})
+@Example("""
+	on portal:
+		wait 1 tick
+		set portal cooldown of event-entity to 5 seconds
+""")
 @Since("2.8.0")
 public class ExprPortalCooldown extends SimplePropertyExpression {
 
@@ -47,16 +44,10 @@ public Timespan convert(Entity entity) {
 	@Override
 	@Nullable
 	public Class>[] acceptChange(ChangeMode mode) {
-		switch (mode) {
-			case SET:
-			case ADD:
-			case RESET:
-			case DELETE:
-			case REMOVE:
-				return CollectionUtils.array(Timespan.class);
-			default:
-				return null;
-		}
+		return switch (mode) {
+			case SET, ADD, RESET, DELETE, REMOVE -> CollectionUtils.array(Timespan.class);
+			default -> null;
+		};
 	}
 
 	@Override
diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang
index d050ec1fa9a..ae452cce405 100644
--- a/src/main/resources/lang/default.lang
+++ b/src/main/resources/lang/default.lang
@@ -2680,6 +2680,13 @@ damage types:
 	wither: wither
 	wither_skull: wither skull
 
+# -- Portal Types --
+portal types:
+	custom: custom
+	end_gateway: end gateway
+	ender: end portal
+	nether: nether portal
+
 # -- Boolean --
 boolean:
 	true:
@@ -2797,6 +2804,7 @@ types:
 	villagercareerchangereason: villager career change reason¦s @a
 	damagesource: damage source¦s @a
 	damagetype: damage type¦s @a
+	portaltype: portal type¦s @a
 
 	# Skript
 	weathertype: weather type¦s @a
diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/EvtPortalTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/EvtPortalTest.java
new file mode 100644
index 00000000000..74fc9fbd0c8
--- /dev/null
+++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/EvtPortalTest.java
@@ -0,0 +1,41 @@
+package org.skriptlang.skript.test.tests.syntaxes.expressions;
+
+import ch.njol.skript.test.runner.SkriptJUnitTest;
+import org.bukkit.PortalType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.entity.EntityPortalEnterEvent;
+import org.bukkit.event.entity.EntityPortalExitEvent;
+import org.bukkit.event.player.PlayerPortalEvent;
+import org.bukkit.event.player.PlayerTeleportEvent;
+import org.bukkit.event.world.PortalCreateEvent;
+import org.bukkit.util.Vector;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+public class EvtPortalTest extends SkriptJUnitTest {
+
+	static {
+		setShutdownDelay(1);
+	}
+
+	private Player player;
+
+	@Before
+	public void setup() {
+		player = EasyMock.niceMock(Player.class);
+	}
+
+	@Test
+	public void test() {
+		new PortalCreateEvent(List.of(), getTestWorld(), null, PortalCreateEvent.CreateReason.NETHER_PAIR).callEvent();
+		new PlayerPortalEvent(player, getTestLocation(), getTestLocation(), PlayerTeleportEvent.TeleportCause.NETHER_PORTAL).callEvent();
+		new PlayerPortalEvent(player, getTestLocation(), getTestLocation(), PlayerTeleportEvent.TeleportCause.END_PORTAL).callEvent();
+		new PlayerPortalEvent(player, getTestLocation(), getTestLocation(), PlayerTeleportEvent.TeleportCause.END_GATEWAY).callEvent();
+		new EntityPortalEnterEvent(player, getTestLocation(), PortalType.NETHER).callEvent();
+		new EntityPortalExitEvent(player, getTestLocation(), getTestLocation(), Vector.getRandom(), Vector.getRandom()).callEvent();
+	}
+
+}
diff --git a/src/test/skript/junit/PortalTest.sk b/src/test/skript/junit/PortalTest.sk
new file mode 100644
index 00000000000..72092204be1
--- /dev/null
+++ b/src/test/skript/junit/PortalTest.sk
@@ -0,0 +1,45 @@
+options:
+	test: "org.skriptlang.skript.test.tests.syntaxes.expressions.EvtPortalTest"
+
+test "EvtPortalTest" when running JUnit:
+	add "portal type - nether" to {_tests::*}
+	add "portal type - end" to {_tests::*}
+	add "portal type - end gateway" to {_tests::*}
+
+	add "portal enter - event" to {_tests::*}
+	add "portal exit - vector overwrite" to {_tests::*}
+	add "portal create - portal type" to {_tests::*}
+	add "portal create - portal blocks" to {_tests::*}
+
+	ensure {@test} completes {_tests::*}
+
+on portal:
+	junit test is {@test}
+	if event-portaltype is nether portal:
+		complete objective "portal type - nether" for junit test {@test}
+	if event-portaltype is end portal:
+		complete objective "portal type - end" for junit test {@test}
+	if event-portaltype is end gateway:
+		complete objective "portal type - end gateway" for junit test {@test}
+
+on portal enter:
+	junit test is {@test}
+	event-portaltype is nether portal
+	complete objective "portal enter - event" for junit test {@test}
+
+on portal exit:
+	junit test is {@test}
+	set event-vector to vector(0, 0, 0)
+	assert event-vector is vector(0, 0, 0) with "event-vector failed to overwrite #1"
+	add 9 to vector x of event-vector
+	assert event-vector is vector(9, 0, 0) with "event-vector failed to overwrite #2"
+	complete objective "portal exit - vector overwrite" for junit test {@test}
+
+on portal create:
+	junit test is {@test}
+	if event-portaltype is nether portal:
+		complete objective "portal create - portal type" for junit test {@test}
+
+	# Event is called with no blocks
+	assert portal blocks is not set with "portal blocks returned the wrong blocks"
+	complete objective "portal create - portal blocks" for junit test {@test}