diff --git a/src/en/manta/AndroidManifest.xml b/src/en/manta/AndroidManifest.xml
new file mode 100644
index 000000000..30deb7f79
--- /dev/null
+++ b/src/en/manta/AndroidManifest.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="eu.kanade.tachiyomi.extension" />
diff --git a/src/en/manta/build.gradle b/src/en/manta/build.gradle
new file mode 100644
index 000000000..bf78b241a
--- /dev/null
+++ b/src/en/manta/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
+
+ext {
+    extName = 'Manta Comics'
+    pkgNameSuffix = 'en.manta'
+    extClass = '.MantaComics'
+    extVersionCode = 1
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/en/manta/res/mipmap-hdpi/ic_launcher.png b/src/en/manta/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..1df104fef
Binary files /dev/null and b/src/en/manta/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/en/manta/res/mipmap-mdpi/ic_launcher.png b/src/en/manta/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..a17a00850
Binary files /dev/null and b/src/en/manta/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/en/manta/res/mipmap-xhdpi/ic_launcher.png b/src/en/manta/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..72b9579f3
Binary files /dev/null and b/src/en/manta/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/en/manta/res/mipmap-xxhdpi/ic_launcher.png b/src/en/manta/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..b2771784a
Binary files /dev/null and b/src/en/manta/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/en/manta/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/manta/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..e05b2b87d
Binary files /dev/null and b/src/en/manta/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/en/manta/res/web_hi_res_512.png b/src/en/manta/res/web_hi_res_512.png
new file mode 100644
index 000000000..c3b3e63b5
Binary files /dev/null and b/src/en/manta/res/web_hi_res_512.png differ
diff --git a/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaAPI.kt b/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaAPI.kt
new file mode 100644
index 000000000..e8b8bb927
--- /dev/null
+++ b/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaAPI.kt
@@ -0,0 +1,127 @@
+package eu.kanade.tachiyomi.extension.en.manta
+
+import kotlinx.serialization.Serializable
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.concurrent.TimeUnit.MILLISECONDS as MS
+
+private val isoDate by lazy {
+    SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT)
+}
+
+private inline val String?.timestamp: Long
+    get() = this?.substringBefore('.')?.let(isoDate::parse)?.time ?: 0L
+
+@Serializable
+data class Series<T : Any>(
+    val data: T,
+    val id: Int,
+    val image: Cover,
+    val episodes: List<Episode>? = null
+) {
+    override fun toString() = data.toString()
+}
+
+@Serializable
+data class Title(private val title: Name) {
+    override fun toString() = title.toString()
+}
+
+@Serializable
+data class Details(
+    val tags: List<Tag>,
+    val isCompleted: Boolean? = null,
+    private val description: Description,
+    private val creators: List<Creator>
+) {
+    val artists by lazy {
+        creators.filter { it.role == "Illustration" }
+    }
+
+    val authors by lazy {
+        creators.filter { it.role != "Illustration" }.ifEmpty { creators }
+    }
+
+    override fun toString() = description.toString()
+}
+
+@Serializable
+data class Episode(
+    val id: Int,
+    val ord: Int,
+    val data: Data?,
+    private val createdAt: String,
+    val cutImages: List<Image>? = null
+) {
+    val timestamp: Long
+        get() = createdAt.timestamp
+
+    val isLocked: Boolean
+        get() = timeTillFree > 0
+
+    val waitingTime: String
+        get() = when (val days = MS.toDays(timeTillFree)) {
+            0L -> "later today"
+            1L -> "tomorrow"
+            else -> "in $days days"
+        }
+
+    private val timeTillFree by lazy {
+        data?.freeAt.timestamp - System.currentTimeMillis()
+    }
+
+    override fun toString() = buildString {
+        append(data?.title ?: "Episode $ord")
+        if (isLocked) append(" \uD83D\uDD12")
+    }
+}
+
+@Serializable
+data class Data(
+    val title: String? = null,
+    val freeAt: String? = null
+)
+
+@Serializable
+data class Creator(
+    private val name: String,
+    val role: String
+) {
+    override fun toString() = name
+}
+
+@Serializable
+data class Description(
+    private val long: String,
+    private val short: String
+) {
+    override fun toString() = "$short\n\n$long"
+}
+
+@Serializable
+data class Cover(private val `1280x1840_480`: Image) {
+    override fun toString() = `1280x1840_480`.toString()
+}
+
+@Serializable
+data class Image(private val downloadUrl: String) {
+    override fun toString() = downloadUrl
+}
+
+@Serializable
+data class Tag(private val name: Name) {
+    override fun toString() = name.toString()
+}
+
+@Serializable
+data class Name(private val en: String) {
+    override fun toString() = en
+}
+
+@Serializable
+data class Status(
+    private val description: String,
+    private val message: String
+) {
+    override fun toString() = "$description: $message"
+}
diff --git a/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaComics.kt b/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaComics.kt
new file mode 100644
index 000000000..83e1cb846
--- /dev/null
+++ b/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaComics.kt
@@ -0,0 +1,128 @@
+package eu.kanade.tachiyomi.extension.en.manta
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservable
+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 kotlinx.serialization.json.Json
+import kotlinx.serialization.json.decodeFromJsonElement
+import kotlinx.serialization.json.jsonObject
+import okhttp3.Request
+import okhttp3.Response
+import uy.kohesive.injekt.injectLazy
+
+class MantaComics : HttpSource() {
+    override val name = "Manta"
+
+    override val lang = "en"
+
+    override val baseUrl = "https://manta.net"
+
+    override val supportsLatest = false
+
+    private val json by injectLazy<Json>()
+
+    override fun headersBuilder() = super.headersBuilder()
+        .set("User-Agent", "Manta/167").set("Origin", baseUrl)
+
+    override fun latestUpdatesRequest(page: Int) =
+        GET("$baseUrl/manta/v1/search/series?cat=New", headers)
+
+    override fun fetchPopularManga(page: Int) =
+        latestUpdatesRequest(page).fetch(::searchMangaParse)
+
+    override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
+        filters.category.ifEmpty { if (query.isEmpty()) "New" else "" }.let {
+            GET("$baseUrl/manta/v1/search/series?cat=$it&q=$query", headers)
+        }
+
+    override fun searchMangaParse(response: Response) =
+        response.parse<List<Series<Title>>>().map {
+            SManga.create().apply {
+                title = it.toString()
+                url = it.id.toString()
+                thumbnail_url = it.image.toString()
+            }
+        }.let { MangasPage(it, false) }
+
+    override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
+        searchMangaRequest(page, query, filters).fetch(::searchMangaParse)
+
+    // Request the actual manga URL for the webview
+    override fun mangaDetailsRequest(manga: SManga) =
+        GET("$baseUrl/series/${manga.url}")
+
+    override fun mangaDetailsParse(response: Response) =
+        SManga.create().apply {
+            val data = response.parse<Series<Details>>().data
+            description = data.toString()
+            genre = data.tags.joinToString()
+            artist = data.artists.joinToString()
+            author = data.authors.joinToString()
+            status = when (data.isCompleted) {
+                true -> SManga.COMPLETED
+                else -> SManga.ONGOING
+            }
+            initialized = true
+        }
+
+    override fun fetchMangaDetails(manga: SManga) =
+        chapterListRequest(manga).fetch(::mangaDetailsParse)
+
+    override fun chapterListRequest(manga: SManga) =
+        GET("$baseUrl/front/v1/series/${manga.url}", headers)
+
+    override fun chapterListParse(response: Response) =
+        response.parse<Series<Title>>().episodes!!.map {
+            SChapter.create().apply {
+                name = it.toString()
+                url = it.id.toString()
+                date_upload = it.timestamp
+                chapter_number = it.ord.toFloat()
+            }
+        }
+
+    override fun fetchChapterList(manga: SManga) =
+        chapterListRequest(manga).fetch(::chapterListParse)
+
+    override fun pageListRequest(chapter: SChapter) =
+        GET("$baseUrl/front/v1/episodes/${chapter.url}", headers)
+
+    override fun pageListParse(response: Response) =
+        response.parse<Episode>().run {
+            if (!isLocked) return@run cutImages!!
+            error("This episode will be available $waitingTime.")
+        }.mapIndexed { idx, img -> Page(idx, "", img.toString()) }
+
+    override fun fetchPageList(chapter: SChapter) =
+        pageListRequest(chapter).fetch(::pageListParse)
+
+    override fun getFilterList() = FilterList(Category())
+
+    override fun latestUpdatesParse(response: Response) =
+        throw UnsupportedOperationException("Not used")
+
+    override fun popularMangaRequest(page: Int) =
+        throw UnsupportedOperationException("Not used")
+
+    override fun popularMangaParse(response: Response) =
+        throw UnsupportedOperationException("Not used")
+
+    override fun imageUrlParse(response: Response) =
+        throw UnsupportedOperationException("Not used")
+
+    private fun <R> Request.fetch(parse: (Response) -> R) =
+        client.newCall(this).asObservable().map { res ->
+            if (res.isSuccessful) return@map parse(res)
+            error(res.parse<Status>("status").toString())
+        }!!
+
+    private inline fun <reified T> Response.parse(key: String = "data") =
+        json.decodeFromJsonElement<T>(
+            json.parseToJsonElement(body!!.string()).jsonObject[key]!!
+        )
+}
diff --git a/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaFilters.kt b/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaFilters.kt
new file mode 100644
index 000000000..504803764
--- /dev/null
+++ b/src/en/manta/src/eu/kanade/tachiyomi/extension/en/manta/MantaFilters.kt
@@ -0,0 +1,23 @@
+package eu.kanade.tachiyomi.extension.en.manta
+
+import eu.kanade.tachiyomi.source.model.Filter
+
+private val categories = arrayOf(
+    "",
+    "New",
+    "Exclusive",
+    "Completed",
+    "Romance",
+    "BL / GL",
+    "Drama",
+    "Fantasy",
+    "Thriller",
+    "Slice of life"
+)
+
+class Category(
+    values: Array<String> = categories
+) : Filter.Select<String>("Category", values)
+
+inline val List<Filter<*>>.category: String
+    get() = (firstOrNull() as? Category)?.run { values[state] } ?: ""