diff --git a/src/en/weebcentral/build.gradle b/src/en/weebcentral/build.gradle
new file mode 100644
index 000000000..ac13a189c
--- /dev/null
+++ b/src/en/weebcentral/build.gradle
@@ -0,0 +1,8 @@
+ext {
+    extName = 'Weeb Central'
+    extClass = '.WeebCentral'
+    extVersionCode = 1
+    isNsfw = true
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/en/weebcentral/res/mipmap-hdpi/ic_launcher.png b/src/en/weebcentral/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..ccadbf82d
Binary files /dev/null and b/src/en/weebcentral/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/en/weebcentral/res/mipmap-mdpi/ic_launcher.png b/src/en/weebcentral/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..25063f087
Binary files /dev/null and b/src/en/weebcentral/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/en/weebcentral/res/mipmap-xhdpi/ic_launcher.png b/src/en/weebcentral/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..9c288a810
Binary files /dev/null and b/src/en/weebcentral/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/en/weebcentral/res/mipmap-xxhdpi/ic_launcher.png b/src/en/weebcentral/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..47acb6883
Binary files /dev/null and b/src/en/weebcentral/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/en/weebcentral/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/weebcentral/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..7f6b1532a
Binary files /dev/null and b/src/en/weebcentral/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/en/weebcentral/src/eu/kanade/tachiyomi/extension/en/weebcentral/Filters.kt b/src/en/weebcentral/src/eu/kanade/tachiyomi/extension/en/weebcentral/Filters.kt
new file mode 100644
index 000000000..a1190f626
--- /dev/null
+++ b/src/en/weebcentral/src/eu/kanade/tachiyomi/extension/en/weebcentral/Filters.kt
@@ -0,0 +1,164 @@
+package eu.kanade.tachiyomi.extension.en.weebcentral
+
+import eu.kanade.tachiyomi.source.model.Filter
+import okhttp3.HttpUrl
+
+interface UriFilter {
+    fun addToUri(builder: HttpUrl.Builder)
+}
+
+open class UriPartFilter(
+    name: String,
+    private val param: String,
+    private val vals: Array<Pair<String, String>>,
+    private val default: String = "",
+) : UriFilter, Filter.Select<String>(
+    name,
+    vals.map { it.first }.toTypedArray(),
+    vals.indexOfFirst { it.second == default }.takeIf { it != -1 } ?: 0,
+) {
+    override fun addToUri(builder: HttpUrl.Builder) {
+        builder.addQueryParameter(param, vals[state].second)
+    }
+}
+
+open class UriMultiSelectOption(name: String, val value: String) : Filter.CheckBox(name)
+
+open class UriMultiSelectFilter(
+    name: String,
+    private val param: String,
+    private val options: Array<Pair<String, String>>,
+) : UriFilter, Filter.Group<UriMultiSelectOption>(
+    name,
+    options.map { UriMultiSelectOption(it.first, it.second) },
+) {
+    override fun addToUri(builder: HttpUrl.Builder) {
+        state.filter { it.state }.forEach {
+            builder.addQueryParameter(param, it.value)
+        }
+    }
+}
+
+open class UriMultiTriSelectOption(name: String, val value: String) : Filter.TriState(name)
+
+open class UriMultiTriSelectFilter(
+    name: String,
+    private val includeUrlParameter: String,
+    private val excludeUrlParameter: String,
+    private val options: Array<Pair<String, String>>,
+) : UriFilter, Filter.Group<UriMultiTriSelectOption>(
+    name,
+    options.map { UriMultiTriSelectOption(it.first, it.second) },
+) {
+    override fun addToUri(builder: HttpUrl.Builder) {
+        state.forEach {
+            if (it.isIncluded()) {
+                builder.addQueryParameter(includeUrlParameter, it.value)
+            }
+            if (it.isExcluded()) {
+                builder.addQueryParameter(excludeUrlParameter, it.value)
+            }
+        }
+    }
+}
+
+class SortFilter(default: String = "") : UriPartFilter(
+    "Sort",
+    "sort",
+    arrayOf(
+        Pair("Best Match", "Best Match"),
+        Pair("Alphabet", "Alphabet"),
+        Pair("Popularity", "Popularity"),
+        Pair("Subscribers", "Subscribers"),
+        Pair("Recently Added", "Recently Added"),
+        Pair("Latest Updates", "Latest Updates"),
+    ),
+    default,
+)
+
+class SortOrderFilter : UriPartFilter(
+    "Sort Order",
+    "order",
+    arrayOf(
+        Pair("Ascending", "Ascending"),
+        Pair("Descending", "Descending"),
+    ),
+)
+
+class OfficialTranslationFilter : UriPartFilter(
+    "Official Translation",
+    "official",
+    arrayOf(
+        Pair("Any", "Any"),
+        Pair("True", "True"),
+        Pair("False", "False"),
+    ),
+)
+
+class StatusFilter : UriMultiSelectFilter(
+    "Series Status",
+    "included_status",
+    arrayOf(
+        Pair("Ongoing", "Ongoing"),
+        Pair("Complete", "Complete"),
+        Pair("Hiatus", "Hiatus"),
+        Pair("Canceled", "Canceled"),
+    ),
+)
+
+class TypeFilter : UriMultiSelectFilter(
+    "Series Type",
+    "included_type",
+    arrayOf(
+        Pair("Manga", "Manga"),
+        Pair("Manhwa", "Manhwa"),
+        Pair("Manhua", "Manhua"),
+        Pair("OEL", "OEL"),
+    ),
+)
+
+class TagFilter : UriMultiTriSelectFilter(
+    "Tags",
+    "included_tag",
+    "excluded_tag",
+    arrayOf(
+        Pair("Action", "Action"),
+        Pair("Adult", "Adult"),
+        Pair("Adventure", "Adventure"),
+        Pair("Comedy", "Comedy"),
+        Pair("Doujinshi", "Doujinshi"),
+        Pair("Drama", "Drama"),
+        Pair("Ecchi", "Ecchi"),
+        Pair("Fantasy", "Fantasy"),
+        Pair("Gender Bender", "Gender Bender"),
+        Pair("Harem", "Harem"),
+        Pair("Hentai", "Hentai"),
+        Pair("Historical", "Historical"),
+        Pair("Horror", "Horror"),
+        Pair("Isekai", "Isekai"),
+        Pair("Josei", "Josei"),
+        Pair("Lolicon", "Lolicon"),
+        Pair("Martial Arts", "Martial Arts"),
+        Pair("Mature", "Mature"),
+        Pair("Mecha", "Mecha"),
+        Pair("Mystery", "Mystery"),
+        Pair("Psychological", "Psychological"),
+        Pair("Romance", "Romance"),
+        Pair("School Life", "School Life"),
+        Pair("Sci-fi", "Sci-fi"),
+        Pair("Seinen", "Seinen"),
+        Pair("Shotacon", "Shotacon"),
+        Pair("Shoujo", "Shoujo"),
+        Pair("Shoujo Ai", "Shoujo Ai"),
+        Pair("Shounen", "Shounen"),
+        Pair("Shounen Ai", "Shounen Ai"),
+        Pair("Slice of Life", "Slice of Life"),
+        Pair("Smut", "Smut"),
+        Pair("Sports", "Sports"),
+        Pair("Supernatural", "Supernatural"),
+        Pair("Tragedy", "Tragedy"),
+        Pair("Yaoi", "Yaoi"),
+        Pair("Yuri", "Yuri"),
+        Pair("Other", "Other"),
+    ),
+)
diff --git a/src/en/weebcentral/src/eu/kanade/tachiyomi/extension/en/weebcentral/WeebCentral.kt b/src/en/weebcentral/src/eu/kanade/tachiyomi/extension/en/weebcentral/WeebCentral.kt
new file mode 100644
index 000000000..4249e5bfb
--- /dev/null
+++ b/src/en/weebcentral/src/eu/kanade/tachiyomi/extension/en/weebcentral/WeebCentral.kt
@@ -0,0 +1,184 @@
+package eu.kanade.tachiyomi.extension.en.weebcentral
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.interceptor.rateLimit
+import eu.kanade.tachiyomi.source.model.FilterList
+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.ParsedHttpSource
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.Request
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+class WeebCentral : ParsedHttpSource() {
+
+    override val name = "Weeb Central"
+
+    override val baseUrl = "https://weebcentral.com"
+
+    override val lang = "en"
+
+    override val supportsLatest = true
+
+    override val client = network.cloudflareClient.newBuilder()
+        .rateLimit(2)
+        .build()
+
+    override fun headersBuilder() = super.headersBuilder()
+        .add("Referer", "$baseUrl/")
+
+    private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
+
+    // ============================== Popular ===============================
+
+    override fun popularMangaRequest(page: Int): Request = searchMangaRequest(
+        page,
+        "",
+        defaultFilterList(SortFilter("Popularity")),
+    )
+
+    override fun popularMangaSelector(): String = searchMangaSelector()
+
+    override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element)
+
+    override fun popularMangaNextPageSelector(): String = searchMangaNextPageSelector()
+
+    // =============================== Latest ===============================
+
+    override fun latestUpdatesRequest(page: Int): Request = searchMangaRequest(
+        page,
+        "",
+        defaultFilterList(SortFilter("Latest Updates")),
+    )
+
+    override fun latestUpdatesSelector(): String = searchMangaSelector()
+
+    override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element)
+
+    override fun latestUpdatesNextPageSelector(): String = searchMangaNextPageSelector()
+
+    // =============================== Search ===============================
+
+    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+        val filterList = filters.ifEmpty { getFilterList() }
+        val url = "$baseUrl/search".toHttpUrl().newBuilder().apply {
+            addQueryParameter("text", query)
+            filterList.filterIsInstance<UriFilter>().forEach {
+                it.addToUri(this)
+            }
+            addQueryParameter("limit", FETCH_LIMIT.toString())
+            addQueryParameter("offset", ((page - 1) * FETCH_LIMIT).toString())
+        }.build()
+
+        return GET(url, headers)
+    }
+
+    override fun searchMangaSelector(): String = "#search-results > article:not(#search-more-container)"
+
+    override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
+        thumbnail_url = element.selectFirst("img")!!.attr("abs:src")
+        with(element.selectFirst("div > a")!!) {
+            title = text()
+            setUrlWithoutDomain(attr("abs:href"))
+        }
+    }
+
+    override fun searchMangaNextPageSelector(): String = "#search-more-container > button"
+
+    // =============================== Filters ==============================
+
+    override fun getFilterList(): FilterList = defaultFilterList(SortFilter())
+
+    // =========================== Manga Details ============================
+
+    override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
+        with(document.select("section[x-data] > section")[0]) {
+            thumbnail_url = selectFirst("img")!!.attr("abs:src")
+            author = select("ul > li:has(strong:contains(Author)) > span > a").joinToString { it.text() }
+            genre = select("ul > li:has(strong:contains(Tag)) > span > a").joinToString { it.text() }
+            status = selectFirst("ul > li:has(strong:contains(Status)) > a").parseStatus()
+        }
+
+        with(document.select("section[x-data] > section")[1]) {
+            title = selectFirst("h1")!!.text()
+            description = selectFirst("li:has(strong:contains(Description)) > p")?.text()
+                ?.replace("NOTE: ", "\n\nNOTE: ")
+        }
+    }
+
+    private fun Element?.parseStatus(): Int = when (this?.text()?.lowercase()) {
+        "ongoing" -> SManga.ONGOING
+        "complete" -> SManga.COMPLETED
+        "hiatus" -> SManga.ON_HIATUS
+        "canceled" -> SManga.CANCELLED
+        else -> SManga.UNKNOWN
+    }
+
+    // ============================== Chapters ==============================
+
+    override fun chapterListRequest(manga: SManga): Request {
+        val url = (baseUrl + manga.url).toHttpUrl().newBuilder().apply {
+            removePathSegment(2)
+            addPathSegment("full-chapter-list")
+        }.build()
+
+        return GET(url, headers)
+    }
+
+    override fun chapterListSelector() = "a"
+
+    override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
+        name = element.selectFirst("span.flex")!!.text()
+        setUrlWithoutDomain(element.attr("abs:href"))
+        element.selectFirst("time[datetime]")?.also {
+            date_upload = it.attr("datetime").parseDate()
+        }
+    }
+
+    private fun String.parseDate(): Long {
+        return try {
+            dateFormat.parse(this)!!.time
+        } catch (_: ParseException) {
+            0L
+        }
+    }
+    // =============================== Pages ================================
+
+    override fun pageListParse(document: Document): List<Page> {
+        return document.select("section[x-data~=scroll] > img").mapIndexed { index, element ->
+            Page(index, imageUrl = element.attr("abs:src"))
+        }
+    }
+
+    override fun imageUrlParse(document: Document) =
+        throw UnsupportedOperationException()
+
+    override fun imageRequest(page: Page): Request {
+        val imgHeaders = headersBuilder().apply {
+            add("Accept", "image/avif,image/webp,*/*")
+            add("Host", page.imageUrl!!.toHttpUrl().host)
+        }.build()
+
+        return GET(page.imageUrl!!, imgHeaders)
+    }
+
+    // ============================= Utilities ==============================
+
+    private fun defaultFilterList(sortFilter: SortFilter): FilterList = FilterList(
+        sortFilter,
+        SortOrderFilter(),
+        OfficialTranslationFilter(),
+        StatusFilter(),
+        TypeFilter(),
+        TagFilter(),
+    )
+
+    companion object {
+        const val FETCH_LIMIT = 24
+    }
+}