diff --git a/fabric-client-tags-api-v1/build.gradle b/fabric-client-tags-api-v1/build.gradle
new file mode 100644
index 000000000..db40cf109
--- /dev/null
+++ b/fabric-client-tags-api-v1/build.gradle
@@ -0,0 +1,11 @@
+archivesBaseName = "fabric-client-tags-api-v1"
+version = getSubprojectVersion(project)
+
+moduleDependencies(project, [
+		'fabric-api-base'
+])
+
+testDependencies(project, [
+		':fabric-convention-tags-v1',
+		':fabric-lifecycle-events-v1',
+])
diff --git a/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/api/tag/client/v1/ClientTags.java b/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/api/tag/client/v1/ClientTags.java
new file mode 100644
index 000000000..cd98f012f
--- /dev/null
+++ b/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/api/tag/client/v1/ClientTags.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.api.tag.client.v1;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.tag.TagKey;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.util.registry.RegistryEntry;
+import net.minecraft.util.registry.RegistryKey;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.impl.tag.client.ClientTagsLoader;
+
+/**
+ * Allows the use of tags by directly loading them from the installed mods.
+ *
+ * <p>Tags are loaded by the server, either the internal server in singleplayer or the connected server and
+ * synced to the client. This can be a pain point for interoperability, as a tag that does not exist on the server
+ * because it is part of a mod only present on the client will no longer be available to the client that may wish to
+ * query it.
+ *
+ * <p>Client Tags resolve that issue by lazily reading the tag json files within the mods on the side of the caller,
+ * directly, allowing for mods to query tags such as {@link net.fabricmc.fabric.api.tag.convention.v1.ConventionalBlockTags}
+ * even when connected to a vanilla server.
+ */
+@Environment(EnvType.CLIENT)
+public final class ClientTags {
+	private static final Map<TagKey<?>, Set<Identifier>> LOCAL_TAG_CACHE = new ConcurrentHashMap<>();
+
+	private ClientTags() {
+	}
+
+	/**
+	 * Loads a tag into the cache, recursively loading any contained tags along with it.
+	 *
+	 * @param tagKey the {@code TagKey} to load
+	 * @return a set of {@code Identifier}s this tag contains
+	 */
+	public static Set<Identifier> getOrCreateLocalTag(TagKey<?> tagKey) {
+		Set<Identifier> ids = LOCAL_TAG_CACHE.get(tagKey);
+
+		if (ids == null) {
+			ids = ClientTagsLoader.loadTag(tagKey);
+			LOCAL_TAG_CACHE.put(tagKey, ids);
+		}
+
+		return ids;
+	}
+
+	/**
+	 * Checks if an entry is in a tag.
+	 *
+	 * <p>If the synced tag does exist, it is queried. If it does not exist,
+	 * the tag populated from the available mods is checked.
+	 *
+	 * @param tagKey the {@code TagKey} to being checked
+	 * @param entry  the entry to check
+	 * @return if the entry is in the given tag
+	 */
+	@SuppressWarnings("unchecked")
+	public static <T> boolean isInWithLocalFallback(TagKey<T> tagKey, T entry) {
+		Objects.requireNonNull(tagKey);
+		Objects.requireNonNull(entry);
+
+		Optional<? extends Registry<?>> maybeRegistry = getRegistry(tagKey);
+
+		if (maybeRegistry.isEmpty()) {
+			return false;
+		}
+
+		if (!tagKey.isOf(maybeRegistry.get().getKey())) {
+			return false;
+		}
+
+		Registry<T> registry = (Registry<T>) maybeRegistry.get();
+
+		Optional<RegistryKey<T>> maybeKey = registry.getKey(entry);
+
+		// Check synced tag
+		if (registry.containsTag(tagKey)) {
+			return maybeKey.filter(registryKey -> registry.entryOf(registryKey).isIn(tagKey))
+					.isPresent();
+		}
+
+		// Check local tags
+		Set<Identifier> ids = getOrCreateLocalTag(tagKey);
+		return maybeKey.filter(registryKey -> ids.contains(registryKey.getValue())).isPresent();
+	}
+
+	/**
+	 * Checks if an entry is in a tag, for use with entries from a dynamic registry,
+	 * such as {@link net.minecraft.world.biome.Biome}s.
+	 *
+	 * <p>If the synced tag does exist, it is queried. If it does not exist,
+	 * the tag populated from the available mods is checked.
+	 *
+	 * @param tagKey        the {@code TagKey} to be checked
+	 * @param registryEntry the entry to check
+	 * @return if the entry is in the given tag
+	 */
+	public static <T> boolean isInWithLocalFallback(TagKey<T> tagKey, RegistryEntry<T> registryEntry) {
+		Objects.requireNonNull(tagKey);
+		Objects.requireNonNull(registryEntry);
+
+		// Check if the tag exists in the dynamic registry first
+		Optional<? extends Registry<T>> maybeRegistry = getRegistry(tagKey);
+
+		if (maybeRegistry.isPresent()) {
+			if (maybeRegistry.get().containsTag(tagKey)) {
+				return registryEntry.isIn(tagKey);
+			}
+		}
+
+		if (registryEntry.getKey().isPresent()) {
+			return isInLocal(tagKey, registryEntry.getKey().get());
+		}
+
+		return false;
+	}
+
+	/**
+	 * Checks if an entry is in a tag provided by the available mods.
+	 *
+	 * @param tagKey      the {@code TagKey} to being checked
+	 * @param registryKey the entry to check
+	 * @return if the entry is in the given tag
+	 */
+	public static <T> boolean isInLocal(TagKey<T> tagKey, RegistryKey<T> registryKey) {
+		Objects.requireNonNull(tagKey);
+		Objects.requireNonNull(registryKey);
+
+		if (tagKey.registry().getValue().equals(registryKey.getRegistry())) {
+			// Check local tags
+			Set<Identifier> ids = getOrCreateLocalTag(tagKey);
+			return ids.contains(registryKey.getValue());
+		}
+
+		return false;
+	}
+
+	@SuppressWarnings("unchecked")
+	private static <T> Optional<? extends Registry<T>> getRegistry(TagKey<T> tagKey) {
+		Objects.requireNonNull(tagKey);
+
+		// Check if the tag represents a dynamic registry
+		if (MinecraftClient.getInstance() != null) {
+			if (MinecraftClient.getInstance().world != null) {
+				if (MinecraftClient.getInstance().world.getRegistryManager() != null) {
+					Optional<? extends Registry<T>> maybeRegistry = MinecraftClient.getInstance().world
+							.getRegistryManager().getOptional(tagKey.registry());
+					if (maybeRegistry.isPresent()) return maybeRegistry;
+				}
+			}
+		}
+
+		return (Optional<? extends Registry<T>>) Registry.REGISTRIES.getOrEmpty(tagKey.registry().getValue());
+	}
+}
diff --git a/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/impl/tag/client/ClientTagsLoader.java b/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/impl/tag/client/ClientTagsLoader.java
new file mode 100644
index 000000000..ecb70d623
--- /dev/null
+++ b/fabric-client-tags-api-v1/src/client/java/net/fabricmc/fabric/impl/tag/client/ClientTagsLoader.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.impl.tag.client;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.mojang.serialization.Dynamic;
+import com.mojang.serialization.JsonOps;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.minecraft.tag.TagEntry;
+import net.minecraft.tag.TagFile;
+import net.minecraft.tag.TagKey;
+import net.minecraft.tag.TagManagerLoader;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.util.registry.RegistryKey;
+
+import net.fabricmc.fabric.api.tag.client.v1.ClientTags;
+import net.fabricmc.loader.api.FabricLoader;
+import net.fabricmc.loader.api.ModContainer;
+
+@ApiStatus.Internal
+public class ClientTagsLoader {
+	private static final Logger LOGGER = LoggerFactory.getLogger("fabric-client-tags-api-v1");
+	/**
+	 * Load a given tag from the available mods into a set of {@code Identifier}s.
+	 * Parsing based on {@link net.minecraft.tag.TagGroupLoader#loadTags(net.minecraft.resource.ResourceManager)}
+	 */
+	public static Set<Identifier> loadTag(TagKey<?> tagKey) {
+		var tags = new HashSet<TagEntry>();
+		HashSet<Path> tagFiles = getTagFiles(tagKey.registry(), tagKey.id());
+
+		for (Path tagPath : tagFiles) {
+			try (BufferedReader tagReader = Files.newBufferedReader(tagPath)) {
+				JsonElement jsonElement = JsonParser.parseReader(tagReader);
+				TagFile maybeTagFile = TagFile.CODEC.parse(new Dynamic<>(JsonOps.INSTANCE, jsonElement))
+						.result().orElse(null);
+
+				if (maybeTagFile != null) {
+					if (maybeTagFile.replace()) {
+						tags.clear();
+					}
+
+					tags.addAll(maybeTagFile.entries());
+				}
+			} catch (IOException e) {
+				LOGGER.error("Error loading tag: " + tagKey, e);
+			}
+		}
+
+		HashSet<Identifier> ids = new HashSet<>();
+
+		for (TagEntry tagEntry : tags) {
+			tagEntry.resolve(new TagEntry.ValueGetter<>() {
+				@Nullable
+				@Override
+				public Identifier direct(Identifier id) {
+					return id;
+				}
+
+				@Nullable
+				@Override
+				public Collection<Identifier> tag(Identifier id) {
+					TagKey<?> tag = TagKey.of(tagKey.registry(), id);
+					return ClientTags.getOrCreateLocalTag(tag);
+				}
+			}, ids::add);
+		}
+
+		return Collections.unmodifiableSet(ids);
+	}
+
+	/**
+	 * @param registryKey the RegistryKey of the TagKey
+	 * @param identifier  the Identifier of the tag
+	 * @return the paths to all tag json files within the available mods
+	 */
+	private static HashSet<Path> getTagFiles(RegistryKey<? extends Registry<?>> registryKey, Identifier identifier) {
+		return getTagFiles(TagManagerLoader.getPath(registryKey), identifier);
+	}
+
+	/**
+	 * @return the paths to all tag json files within the available mods
+	 */
+	private static HashSet<Path> getTagFiles(String tagType, Identifier identifier) {
+		String tagFile = "data/%s/%s/%s.json".formatted(identifier.getNamespace(), tagType, identifier.getPath());
+		return getResourcePaths(tagFile);
+	}
+
+	/**
+	 * @return all paths from the available mods that match the given internal path
+	 */
+	private static HashSet<Path> getResourcePaths(String path) {
+		HashSet<Path> out = new HashSet<>();
+
+		for (ModContainer mod : FabricLoader.getInstance().getAllMods()) {
+			mod.findPath(path).ifPresent(out::add);
+		}
+
+		return out;
+	}
+}
diff --git a/fabric-client-tags-api-v1/src/client/resources/assets/fabric-client-tags-api-v1/icon.png b/fabric-client-tags-api-v1/src/client/resources/assets/fabric-client-tags-api-v1/icon.png
new file mode 100644
index 000000000..2931efbf6
Binary files /dev/null and b/fabric-client-tags-api-v1/src/client/resources/assets/fabric-client-tags-api-v1/icon.png differ
diff --git a/fabric-client-tags-api-v1/src/client/resources/fabric.mod.json b/fabric-client-tags-api-v1/src/client/resources/fabric.mod.json
new file mode 100644
index 000000000..504cd5b81
--- /dev/null
+++ b/fabric-client-tags-api-v1/src/client/resources/fabric.mod.json
@@ -0,0 +1,25 @@
+{
+  "schemaVersion": 1,
+  "id": "fabric-client-tags-api-v1",
+  "name": "Fabric Client Tags",
+  "version": "${version}",
+  "environment": "client",
+  "license": "Apache-2.0",
+  "icon": "assets/fabric-client-tags-api-v1/icon.png",
+  "contact": {
+    "homepage": "https://fabricmc.net",
+    "irc": "irc://irc.esper.net:6667/fabric",
+    "issues": "https://github.com/FabricMC/fabric/issues",
+    "sources": "https://github.com/FabricMC/fabric"
+  },
+  "authors": [
+    "FabricMC"
+  ],
+  "depends": {
+    "fabricloader": ">=0.14.6"
+  },
+  "description": "Adds the ability to load tags from the local mods.",
+  "custom": {
+    "fabric-api:module-lifecycle": "stable"
+  }
+}
diff --git a/fabric-client-tags-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/client/v1/ClientTagTest.java b/fabric-client-tags-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/client/v1/ClientTagTest.java
new file mode 100644
index 000000000..eb2d95c5f
--- /dev/null
+++ b/fabric-client-tags-api-v1/src/testmod/java/net/fabricmc/fabric/test/tag/client/v1/ClientTagTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.test.tag.client.v1;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.minecraft.block.Blocks;
+import net.minecraft.world.biome.BiomeKeys;
+
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.fabricmc.fabric.api.tag.client.v1.ClientTags;
+import net.fabricmc.fabric.api.tag.convention.v1.ConventionalBiomeTags;
+import net.fabricmc.fabric.api.tag.convention.v1.ConventionalBlockTags;
+import net.fabricmc.fabric.api.tag.convention.v1.ConventionalEnchantmentTags;
+
+public class ClientTagTest implements ClientModInitializer {
+	private static final Logger LOGGER = LoggerFactory.getLogger(ClientTagTest.class);
+
+	@Override
+	public void onInitializeClient() {
+		ClientLifecycleEvents.CLIENT_STARTED.register(client -> {
+			if (ClientTags.getOrCreateLocalTag(ConventionalEnchantmentTags.INCREASES_BLOCK_DROPS) == null) {
+				throw new AssertionError("Expected to load c:fortune, but it was not found!");
+			}
+
+			if (!ClientTags.isInWithLocalFallback(ConventionalBlockTags.ORES, Blocks.DIAMOND_ORE)) {
+				throw new AssertionError("Expected to find diamond ore in c:ores, but it was not found!");
+			}
+
+			if (ClientTags.isInWithLocalFallback(ConventionalBlockTags.ORES, Blocks.DIAMOND_BLOCK)) {
+				throw new AssertionError("Did not expect to find diamond block in c:ores, but it was found!");
+			}
+
+			if (!ClientTags.isInLocal(ConventionalBiomeTags.FOREST, BiomeKeys.FOREST)) {
+				throw new AssertionError("Expected to find forest in c:forest, but it was not found!");
+			}
+
+			// Success!
+			LOGGER.info("The tests for client tags passed!");
+		});
+	}
+}
diff --git a/fabric-client-tags-api-v1/src/testmod/resources/fabric.mod.json b/fabric-client-tags-api-v1/src/testmod/resources/fabric.mod.json
new file mode 100644
index 000000000..5f4d62b0a
--- /dev/null
+++ b/fabric-client-tags-api-v1/src/testmod/resources/fabric.mod.json
@@ -0,0 +1,16 @@
+{
+  "schemaVersion": 1,
+  "id": "fabric-clients-tags-api-v1-testmod",
+  "name": "Fabric Client Tags API (v1) Test Mod",
+  "version": "1.0.0",
+  "environment": "*",
+  "license": "Apache-2.0",
+  "depends": {
+    "fabric-convention-tags-v1": "*"
+  },
+  "entrypoints": {
+    "client": [
+      "net.fabricmc.fabric.test.tag.client.v1.ClientTagTest"
+    ]
+  }
+}
diff --git a/gradle.properties b/gradle.properties
index f72d11f02..09da69d3c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -56,3 +56,4 @@ fabric-textures-v0-version=1.0.18
 fabric-transfer-api-v1-version=2.0.9
 fabric-transitive-access-wideners-v1-version=1.1.1
 fabric-convention-tags-v1-version=1.0.8
+fabric-client-tags-api-v1-version=1.0.0
diff --git a/settings.gradle b/settings.gradle
index ac0d45fab..2cb8fd438 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -50,6 +50,7 @@ include 'fabric-screen-handler-api-v1'
 include 'fabric-textures-v0'
 include 'fabric-transfer-api-v1'
 include 'fabric-convention-tags-v1'
+include 'fabric-client-tags-api-v1'
 include 'fabric-transitive-access-wideners-v1'
 
 include 'deprecated'