diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc
index 4fcad5f26..249bebbd1 100644
--- a/src/libfetchers/github.cc
+++ b/src/libfetchers/github.cc
@@ -607,8 +607,83 @@ struct SourceHutInputScheme : GitArchiveInputScheme
     }
 };
 
+struct GiteaInputScheme : GitArchiveInputScheme
+{
+    std::string_view schemeName() const override { return "gitea"; }
+
+    std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
+    {
+        // Gitea supports OAuth2 tokens and HTTP Basic
+        // Authentication.  The former simply specifies the token, the
+        // latter can use the token as the password.  Only the first
+        // is used here. See
+        // https://docs.gitea.com/development/api-usage#authentication
+        return std::pair<std::string, std::string>("Authorization", fmt("token %s", token));
+    }
+
+    std::string getHost(const Input & input) const
+    {
+        return maybeGetStrAttr(input.attrs, "host").value_or("codeberg.org");
+    }
+
+    std::string getOwner(const Input & input) const
+    {
+        return getStrAttr(input.attrs, "owner");
+    }
+
+    std::string getRepo(const Input & input) const
+    {
+        return getStrAttr(input.attrs, "repo");
+    }
+
+    RefInfo getRevFromRef(nix::ref<Store> store, const Input & input) const override
+    {
+        auto host = getHost(input);
+        auto url = fmt("https://%s/api/v1/repos/%s/%s/commits?sha=%s", host, getOwner(input), getRepo(input), *input.getRef());
+
+        Headers headers = makeHeadersWithAuthTokens(*input.settings, host);
+
+        auto json = nlohmann::json::parse(
+            readFile(
+                store->toRealPath(
+                    downloadFile(store, url, "source", headers).storePath)));
+
+        return RefInfo {
+            .rev = Hash::parseAny(std::string { json[1]["sha"] }, HashAlgorithm::SHA1),
+            .treeHash = Hash::parseAny(std::string { json[1]["commit"]["tree"]["sha"] }, HashAlgorithm::SHA1)
+        };
+    }
+
+    DownloadUrl getDownloadUrl(const Input & input) const override
+    {
+        auto host = getHost(input);
+
+        Headers headers = makeHeadersWithAuthTokens(*input.settings, host);
+
+        // If we have no auth headers then we default to the public archive
+        // urls so we do not run into rate limits.
+        const auto urlFmt = headers.empty() ? "https://%s/%s/%s/archive/%s.tar.gz" : "https://%s/api/v1/repos/%s/%s/archive/%s.tar.gz";
+
+        const auto url = fmt(urlFmt, host, getOwner(input), getRepo(input),
+            input.getRev()->to_string(HashFormat::Base16, false));
+
+        return DownloadUrl { url, headers };
+    }
+
+    void clone(const Input & input, const Path & destDir) const override
+    {
+        auto host = getHost(input);
+        Input::fromURL(*input.settings, fmt("git+https://%s/%s/%s.git",
+                host, getOwner(input), getRepo(input)))
+            .applyOverrides(input.getRef(), input.getRev())
+            .clone(destDir);
+    }
+};
+
+
 static auto rGitHubInputScheme = OnStartup([] { registerInputScheme(std::make_unique<GitHubInputScheme>()); });
 static auto rGitLabInputScheme = OnStartup([] { registerInputScheme(std::make_unique<GitLabInputScheme>()); });
 static auto rSourceHutInputScheme = OnStartup([] { registerInputScheme(std::make_unique<SourceHutInputScheme>()); });
+static auto rGiteaInputScheme = OnStartup([] { registerInputScheme(std::make_unique<GiteaInputScheme>()); });
 
 }
diff --git a/src/nix/flake.md b/src/nix/flake.md
index 364302b61..7a0513d26 100644
--- a/src/nix/flake.md
+++ b/src/nix/flake.md
@@ -390,6 +390,33 @@ Currently the `type` attribute can be one of the following:
   * `sourcehut:~misterio/nix-colors/182b4b8709b8ffe4e9774a4c5d6877bf6bb9a21c`
   * `sourcehut:~misterio/nix-colors/21c1a380a6915d890d408e9f22203436a35bb2de?host=hg.sr.ht`
 
+* `gitea`: Similar to `github`, is a more efficient way to fetch
+  Gitea/Forgejo repositories. The default host is `codeberg.org`.
+  The following attributes are required:
+
+  * `owner`: The owner of the repository.
+
+  * `repo`: The name of the repository.
+
+  Like `github`, these are downloaded as tarball archives.
+
+  The URL syntax for `gitea` flakes is:
+
+  `gitea:<owner>/<repo>(/<rev-or-ref>)?(\?<params>)?`
+
+  `<rev-or-ref>` works the same as `github`. Either a branch or tag name
+  (`ref`), or a commit hash (`rev`) can be specified.
+
+  Since Gitea/Forgejo allows for self-hosting, you can specify `host` as
+  a parameter, to point to any instances other than `codeberg.org`.
+
+  Some examples:
+
+  * `gitea:redict/redict`
+  * `gitea:redict/redict/main`
+  * `gitea:redict/redict/a4c81102327bc2c74d229784a1d1dd680c708918`
+  * `gitea:lix-project/lix?host=git.lix.systems`
+
 # Flake format
 
 As an example, here is a simple `flake.nix` that depends on the