diff --git a/src/main/java/chylex/bettercontrols/config/BetterControlsConfig.java b/src/main/java/chylex/bettercontrols/config/BetterControlsConfig.java
index 9db9f0b..d7bab42 100644
--- a/src/main/java/chylex/bettercontrols/config/BetterControlsConfig.java
+++ b/src/main/java/chylex/bettercontrols/config/BetterControlsConfig.java
@@ -11,6 +11,11 @@ public final class BetterControlsConfig{
 	
 	public boolean doubleTapForwardToSprint = true;
 	
+	public float flightSpeedMpCreativeDefault = 1F;
+	public float flightSpeedMpCreativeSprinting = 2F;
+	public float flightSpeedMpSpectatorDefault = 1F;
+	public float flightSpeedMpSpectatorSprinting = 2F;
+	
 	BetterControlsConfig(){}
 	
 	private BetterControlsConfig setPath(final Path path){
diff --git a/src/main/java/chylex/bettercontrols/config/ConfigSerializer.java b/src/main/java/chylex/bettercontrols/config/ConfigSerializer.java
index 4a6e00e..b1a8c83 100644
--- a/src/main/java/chylex/bettercontrols/config/ConfigSerializer.java
+++ b/src/main/java/chylex/bettercontrols/config/ConfigSerializer.java
@@ -32,6 +32,11 @@ final class ConfigSerializer implements JsonSerializer<BetterControlsConfig>, Js
 		
 		Json.setBool(obj, "Sprint.DoubleTapForward", cfg.doubleTapForwardToSprint);
 		
+		Json.setFloat(obj, "Flight.SpeedMp.Creative.Default", cfg.flightSpeedMpCreativeDefault);
+		Json.setFloat(obj, "Flight.SpeedMp.Creative.Sprinting", cfg.flightSpeedMpCreativeSprinting);
+		Json.setFloat(obj, "Flight.SpeedMp.Spectator.Default", cfg.flightSpeedMpSpectatorDefault);
+		Json.setFloat(obj, "Flight.SpeedMp.Spectator.Sprinting", cfg.flightSpeedMpSpectatorSprinting);
+		
 		return obj;
 	}
 	
@@ -42,6 +47,11 @@ final class ConfigSerializer implements JsonSerializer<BetterControlsConfig>, Js
 		
 		cfg.doubleTapForwardToSprint = Json.getBool(obj, "Sprint.DoubleTapForward", cfg.doubleTapForwardToSprint);
 		
+		cfg.flightSpeedMpCreativeDefault = Json.getFloat(obj, "Flight.SpeedMp.Creative.Default", cfg.flightSpeedMpCreativeDefault);
+		cfg.flightSpeedMpCreativeSprinting = Json.getFloat(obj, "Flight.SpeedMp.Creative.Sprinting", cfg.flightSpeedMpCreativeSprinting);
+		cfg.flightSpeedMpSpectatorDefault = Json.getFloat(obj, "Flight.SpeedMp.Spectator.Default", cfg.flightSpeedMpSpectatorDefault);
+		cfg.flightSpeedMpSpectatorSprinting = Json.getFloat(obj, "Flight.SpeedMp.Spectator.Sprinting", cfg.flightSpeedMpSpectatorSprinting);
+		
 		return cfg;
 	}
 	
diff --git a/src/main/java/chylex/bettercontrols/gui/BetterControlsScreen.java b/src/main/java/chylex/bettercontrols/gui/BetterControlsScreen.java
index 233c7e0..a0231d9 100644
--- a/src/main/java/chylex/bettercontrols/gui/BetterControlsScreen.java
+++ b/src/main/java/chylex/bettercontrols/gui/BetterControlsScreen.java
@@ -2,7 +2,9 @@ package chylex.bettercontrols.gui;
 import chylex.bettercontrols.BetterControlsMod;
 import chylex.bettercontrols.config.BetterControlsConfig;
 import chylex.bettercontrols.gui.elements.BooleanValueWidget;
+import chylex.bettercontrols.gui.elements.DiscreteValueSliderWidget;
 import chylex.bettercontrols.gui.elements.KeyBindingWidget;
+import chylex.bettercontrols.gui.elements.Option;
 import chylex.bettercontrols.gui.elements.TextWidget;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.Element;
@@ -17,6 +19,7 @@ import net.minecraft.text.LiteralText;
 import net.minecraft.text.Text;
 import org.lwjgl.glfw.GLFW;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import static chylex.bettercontrols.gui.OptionListWidget.COL2_W;
 import static chylex.bettercontrols.gui.OptionListWidget.ROW_WIDTH;
@@ -43,6 +46,45 @@ public class BetterControlsScreen extends GameOptionsScreen{
 		return y;
 	}
 	
+	@SuppressWarnings({ "AutoBoxing", "AutoUnboxing" })
+	private int generateFlightOptions(int y, final List<Element> elements){
+		final BetterControlsConfig cfg = BetterControlsMod.config;
+		
+		final List<Option<Float>> flightSpeedOptions = Arrays.asList(
+			new Option<>(Float.valueOf(0.25F), Text.of("0.25x")),
+			new Option<>(Float.valueOf(0.50F), Text.of("0.5x")),
+			new Option<>(Float.valueOf(0.75F), Text.of("0.75x")),
+			new Option<>(Float.valueOf(1.00F), Text.of("1x")),
+			new Option<>(Float.valueOf(1.50F), Text.of("1.5x")),
+			new Option<>(Float.valueOf(2.00F), Text.of("2x")),
+			new Option<>(Float.valueOf(3.00F), Text.of("3x")),
+			new Option<>(Float.valueOf(4.00F), Text.of("4x")),
+			new Option<>(Float.valueOf(6.00F), Text.of("6x")),
+			new Option<>(Float.valueOf(8.00F), Text.of("8x"))
+		);
+		
+		generateLeftSideText(y, elements, Text.of("Speed Multiplier (Creative)"));
+		elements.add(new DiscreteValueSliderWidget<>(col2(1), y, COL2_W, flightSpeedOptions, cfg.flightSpeedMpCreativeDefault, value -> cfg.flightSpeedMpCreativeDefault = value));
+		
+		y += ROW_HEIGHT;
+		
+		generateLeftSideText(y, elements, Text.of("Speed Multiplier (Creative + Sprinting)"));
+		elements.add(new DiscreteValueSliderWidget<>(col2(1), y, COL2_W, flightSpeedOptions, cfg.flightSpeedMpCreativeSprinting, value -> cfg.flightSpeedMpCreativeSprinting = value));
+		
+		y += ROW_HEIGHT;
+		
+		generateLeftSideText(y, elements, Text.of("Speed Multiplier (Spectator)"));
+		elements.add(new DiscreteValueSliderWidget<>(col2(1), y, COL2_W, flightSpeedOptions, cfg.flightSpeedMpSpectatorDefault, value -> cfg.flightSpeedMpSpectatorDefault = value));
+		
+		y += ROW_HEIGHT;
+		
+		generateLeftSideText(y, elements, Text.of("Speed Multiplier (Spectator + Sprinting)"));
+		elements.add(new DiscreteValueSliderWidget<>(col2(1), y, COL2_W, flightSpeedOptions, cfg.flightSpeedMpSpectatorSprinting, value -> cfg.flightSpeedMpSpectatorSprinting = value));
+		
+		y += ROW_HEIGHT;
+		return y;
+	}
+	
 	// Helpers
 	
 	private static void generateLeftSideText(final int y, final List<Element> elements, final Text text){
@@ -69,6 +111,9 @@ public class BetterControlsScreen extends GameOptionsScreen{
 		elements.add(new TextWidget(0, y, ROW_WIDTH, ROW_HEIGHT, Text.of("Sprinting"), CENTER));
 		y = generateSprintingOptions(y + ROW_HEIGHT, elements) + TITLE_MARGIN_TOP;
 		
+		elements.add(new TextWidget(0, y, ROW_WIDTH, ROW_HEIGHT, Text.of("Flying"), CENTER));
+		y = generateFlightOptions(y + ROW_HEIGHT, elements) + TITLE_MARGIN_TOP;
+		
 		addButton(new ButtonWidget(width / 2 - 99, height - 29, 200, 20, ScreenTexts.DONE, btn -> client.openScreen(parent)));
 		addChild(optionsWidget = new OptionListWidget(21, height - 32, width, height, elements, y - TITLE_MARGIN_TOP + BOTTOM_PADDING));
 	}
diff --git a/src/main/java/chylex/bettercontrols/mixin/HookClientPlayerTick.java b/src/main/java/chylex/bettercontrols/mixin/HookClientPlayerTick.java
index 0ca4ea2..faf2e62 100644
--- a/src/main/java/chylex/bettercontrols/mixin/HookClientPlayerTick.java
+++ b/src/main/java/chylex/bettercontrols/mixin/HookClientPlayerTick.java
@@ -8,6 +8,7 @@ import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import static org.spongepowered.asm.mixin.injection.At.Shift.AFTER;
 
 @Mixin(ClientPlayerEntity.class)
 public abstract class HookClientPlayerTick extends AbstractClientPlayerEntity{
@@ -20,4 +21,10 @@ public abstract class HookClientPlayerTick extends AbstractClientPlayerEntity{
 		final ClientPlayerEntity player = (ClientPlayerEntity)(Object)this;
 		PlayerTicker.get(player).atHead(player);
 	}
+	
+	@Inject(method = "tickMovement()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/input/Input;tick(Z)V", ordinal = 0, shift = AFTER))
+	private void afterInputTick(final CallbackInfo info){
+		final ClientPlayerEntity player = (ClientPlayerEntity)(Object)this;
+		PlayerTicker.get(player).afterInputTick(player);
+	}
 }
diff --git a/src/main/java/chylex/bettercontrols/player/FlightHelper.java b/src/main/java/chylex/bettercontrols/player/FlightHelper.java
new file mode 100644
index 0000000..d3e38d2
--- /dev/null
+++ b/src/main/java/chylex/bettercontrols/player/FlightHelper.java
@@ -0,0 +1,37 @@
+package chylex.bettercontrols.player;
+import chylex.bettercontrols.BetterControlsMod;
+import chylex.bettercontrols.config.BetterControlsConfig;
+import net.minecraft.client.network.ClientPlayerEntity;
+
+final class FlightHelper{
+	private FlightHelper(){}
+	
+	private static final float BASE_FLIGHT_SPEED = 0.05F;
+	private static final float BASE_FLIGHT_SPEED_SPRINT_MP_INV = 0.5F; // sprinting doubles speed in PlayerEntity.travel
+	
+	private static BetterControlsConfig cfg(){
+		return BetterControlsMod.config;
+	}
+	
+	static float getFlightSpeed(final ClientPlayerEntity player){
+		if (player.isCreative()){
+			if (player.isSprinting()){
+				return BASE_FLIGHT_SPEED * cfg().flightSpeedMpCreativeSprinting * BASE_FLIGHT_SPEED_SPRINT_MP_INV;
+			}
+			else{
+				return BASE_FLIGHT_SPEED * cfg().flightSpeedMpCreativeDefault;
+			}
+		}
+		else if (player.isSpectator()){
+			if (player.isSprinting()){
+				return BASE_FLIGHT_SPEED * cfg().flightSpeedMpSpectatorSprinting * BASE_FLIGHT_SPEED_SPRINT_MP_INV;
+			}
+			else{
+				return BASE_FLIGHT_SPEED * cfg().flightSpeedMpSpectatorDefault;
+			}
+		}
+		else{
+			return 0F;
+		}
+	}
+}
diff --git a/src/main/java/chylex/bettercontrols/player/PlayerTicker.java b/src/main/java/chylex/bettercontrols/player/PlayerTicker.java
index 4610009..3556287 100644
--- a/src/main/java/chylex/bettercontrols/player/PlayerTicker.java
+++ b/src/main/java/chylex/bettercontrols/player/PlayerTicker.java
@@ -38,4 +38,12 @@ public final class PlayerTicker{
 			((AccessClientPlayerFields)player).setTicksLeftToDoubleTapSprint(0);
 		}
 	}
+	
+	public void afterInputTick(final ClientPlayerEntity player){
+		final float flightSpeed = FlightHelper.getFlightSpeed(player);
+		
+		if (flightSpeed > 0F){
+			player.abilities.setFlySpeed(flightSpeed);
+		}
+	}
 }