diff --git a/src/en/mangahen/build.gradle b/src/en/mangahen/build.gradle
new file mode 100644
index 000000000..3246b2c00
--- /dev/null
+++ b/src/en/mangahen/build.gradle
@@ -0,0 +1,8 @@
+ext {
+    extName = 'MangaHen'
+    extClass = '.MangaHen'
+    extVersionCode = 1
+    isNsfw = true
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/en/mangahen/res/mipmap-hdpi/ic_launcher.png b/src/en/mangahen/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..17f27fa2b
Binary files /dev/null and b/src/en/mangahen/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/en/mangahen/res/mipmap-mdpi/ic_launcher.png b/src/en/mangahen/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..0b1a61d1e
Binary files /dev/null and b/src/en/mangahen/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/en/mangahen/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangahen/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..a15def8d7
Binary files /dev/null and b/src/en/mangahen/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/en/mangahen/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangahen/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..8e4b9d5b5
Binary files /dev/null and b/src/en/mangahen/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/en/mangahen/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangahen/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..1becc0835
Binary files /dev/null and b/src/en/mangahen/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/en/mangahen/src/eu/kanade/tachiyomi/extension/en/mangahen/MangaHen.kt b/src/en/mangahen/src/eu/kanade/tachiyomi/extension/en/mangahen/MangaHen.kt
new file mode 100644
index 000000000..5307e1e35
--- /dev/null
+++ b/src/en/mangahen/src/eu/kanade/tachiyomi/extension/en/mangahen/MangaHen.kt
@@ -0,0 +1,174 @@
+package eu.kanade.tachiyomi.extension.en.mangahen
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
+import eu.kanade.tachiyomi.source.model.Page
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.source.online.HttpSource
+import eu.kanade.tachiyomi.util.asJsoup
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.Request
+import okhttp3.Response
+import org.jsoup.nodes.Element
+import rx.Observable
+
+class MangaHen : HttpSource() {
+
+    override val name = "MangaHen"
+
+    override val baseUrl = "https://manga-hen.com"
+
+    private val advSearchURL = "$baseUrl/advanced-search"
+
+    override val lang = "en"
+
+    override val supportsLatest = true
+
+    override val client = network.cloudflareClient
+
+    private var tagsList: List<String> = listOf()
+
+    // Popular
+    override fun popularMangaRequest(page: Int): Request {
+        return GET("$advSearchURL/?search=1&type=0&sort=1&page=$page", headers)
+    }
+
+    override fun popularMangaParse(response: Response): MangasPage {
+        val doc = response.asJsoup()
+
+        val mangas = doc.select("a[href^=/manga/]").map(::popularMangaFromElement)
+
+        val hasNextPage = doc.select("a[href*=page]").any { it.text().isBlank() }
+
+        return MangasPage(mangas, hasNextPage)
+    }
+
+    private fun popularMangaFromElement(element: Element): SManga {
+        return SManga.create().apply {
+            title = element.selectFirst("h2")!!.ownText()
+            setUrlWithoutDomain(element.absUrl("href"))
+            thumbnail_url = element.selectFirst("img")!!.absUrl("src")
+        }
+    }
+
+    // Latest
+    override fun latestUpdatesRequest(page: Int): Request {
+        return GET("$advSearchURL/?search=1&type=0&sort=2&page=$page", headers)
+    }
+
+    override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
+
+    // Search
+
+    private fun tagSearch(tag: String, tagsList: List<String>): String? {
+        val index = (tagsList.indexOf(tag) + 1).toString()
+        return if (index != "-1") index else null
+    }
+
+    private fun tagsList(): List<String> {
+        if (tagsList.isEmpty()) {
+            val request = GET(advSearchURL, headers)
+
+            val response = client.newCall(request).execute()
+
+            tagsList = response.asJsoup().select("li[onclick=updateTag(this)]").map { it.ownText().lowercase() }
+        }
+        return tagsList
+    }
+
+    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+        val includeTags = mutableListOf<String>()
+        val excludeTags = mutableListOf<String>()
+
+        val tagsList = tagsList()
+        val url = advSearchURL.toHttpUrl().newBuilder().apply {
+            filters.forEach {
+                when (it) {
+                    is SortFilter -> addQueryParameter("sort", it.getValue())
+
+                    is TypeFilter -> addQueryParameter("type", it.getValue())
+
+                    is TextFilter -> {
+                        if (it.state.isNotEmpty()) {
+                            it.state.split(",").filter(String::isNotBlank).map { tag ->
+                                val trimmed = tag.trim().lowercase()
+                                if (trimmed.startsWith('-')) {
+                                    tagSearch(trimmed.removePrefix("-"), tagsList)?.let { tagInfo ->
+                                        excludeTags.add(tagInfo)
+                                    }
+                                } else {
+                                    tagSearch(trimmed, tagsList)?.let { tagInfo ->
+                                        includeTags.add(tagInfo)
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    else -> {}
+                }
+            }
+
+            addQueryParameter("name", query)
+
+            addQueryParameter("search", "1")
+            if (includeTags.isNotEmpty()) addQueryParameter("include_tags", includeTags.joinToString())
+            if (excludeTags.isNotEmpty()) addQueryParameter("exclude_tags", excludeTags.joinToString())
+            if (page > 1) addQueryParameter("page", page.toString())
+        }.build()
+
+        return GET(url, headers)
+    }
+
+    override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
+
+    // Details
+
+    override fun mangaDetailsParse(response: Response): SManga {
+        val document = response.asJsoup()
+        return SManga.create().apply {
+            val authors = document.select("a[href*=/circles/]").eachText().joinToString()
+            val artists = document.select("a[href*=/authors/]").eachText().joinToString()
+            initialized = true
+            title = document.select("h1.font-semibold").text()
+            author = authors.ifEmpty { artists }
+            artist = artists
+            genre = document.select("a[href*=/tags/]").eachText().joinToString()
+            description = buildString {
+                append("Categories: ", document.select("a[href*=/categories/]").text(), "\n")
+                append("Parodies: ", document.select("a[href*=/parodies/]").text(), "\n")
+                append("Circles: ", document.select("a[href*=/circles/]").text(), "\n\n")
+                append(document.select("tr:contains(page)").text(), "\n")
+                append(document.select("tr:contains(view)").text(), "\n")
+            }
+            thumbnail_url = document.selectFirst("img[src*=thumbnail].w-96")?.absUrl("src")
+        }
+    }
+
+    // Chapters
+
+    override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
+        return Observable.just(
+            listOf(
+                SChapter.create().apply {
+                    name = "Chapter"
+                    setUrlWithoutDomain(manga.url)
+                },
+            ),
+        )
+    }
+
+    // Pages
+
+    override fun pageListParse(response: Response): List<Page> {
+        val images = response.asJsoup().select("img[src*=images]:not(img[src*=thumbnail]).w-full")
+        return images.mapIndexed { index, image ->
+            Page(index, imageUrl = image.absUrl("src").replace(Regex("-t(?=\\.)"), ""))
+        }
+    }
+
+    override fun getFilterList() = getFilters()
+    override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
+    override fun chapterListParse(response: Response): List<SChapter> = throw UnsupportedOperationException()
+}
diff --git a/src/en/mangahen/src/eu/kanade/tachiyomi/extension/en/mangahen/MangaHenFilters.kt b/src/en/mangahen/src/eu/kanade/tachiyomi/extension/en/mangahen/MangaHenFilters.kt
new file mode 100644
index 000000000..62418adc9
--- /dev/null
+++ b/src/en/mangahen/src/eu/kanade/tachiyomi/extension/en/mangahen/MangaHenFilters.kt
@@ -0,0 +1,40 @@
+package eu.kanade.tachiyomi.extension.en.mangahen
+
+import eu.kanade.tachiyomi.source.model.Filter
+import eu.kanade.tachiyomi.source.model.FilterList
+
+fun getFilters(): FilterList {
+    return FilterList(
+        SortFilter("Sort by", getSortsList),
+        TypeFilter("Types", getTypesList),
+        Filter.Separator(),
+        Filter.Header("Separate tags with commas (,)"),
+        Filter.Header("Prepend with dash (-) to exclude"),
+        TextFilter("Tags"),
+    )
+}
+
+internal open class TextFilter(name: String) : Filter.Text(name)
+
+internal open class SortFilter(name: String, vals: List<Pair<String, String>>) : SelectFilter(name, vals)
+
+internal open class TypeFilter(name: String, vals: List<Pair<String, String>>) : SelectFilter(name, vals)
+
+internal open class SelectFilter(name: String, private val vals: List<Pair<String, String>>, state: Int = 0) :
+    Filter.Select<String>(name, vals.map { it.first }.toTypedArray(), state) {
+    fun getValue() = vals[state].second
+}
+
+private val getTypesList: List<Pair<String, String>> = listOf(
+    Pair("All", "0"),
+    Pair("Manga", "1"),
+    Pair("Doujinshi", "2"),
+)
+
+private val getSortsList: List<Pair<String, String>> = listOf(
+    Pair("Newest", "2"),
+    Pair("Popular", "1"),
+    Pair("Relevance", "0"),
+    Pair("Best Rated", "3"),
+    Pair("Most Viewed", "4"),
+)