diff --git a/src/vi/nettruyen/build.gradle b/src/vi/nettruyen/build.gradle
new file mode 100644
index 000000000..34f9a3f19
--- /dev/null
+++ b/src/vi/nettruyen/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+ext {
+    appName = 'Tachiyomi: NetTruyen'
+    pkgNameSuffix = 'vi.nettruyen'
+    extClass = '.NetTruyen'
+    extVersionCode = 1
+    libVersion = '1.2'
+}
+
+apply from: "$rootDir/common.gradle"
\ No newline at end of file
diff --git a/src/vi/nettruyen/res/mipmap-hdpi/ic_launcher.png b/src/vi/nettruyen/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..f71920fa3
Binary files /dev/null and b/src/vi/nettruyen/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/vi/nettruyen/res/mipmap-mdpi/ic_launcher.png b/src/vi/nettruyen/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..9bb74c829
Binary files /dev/null and b/src/vi/nettruyen/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/vi/nettruyen/res/mipmap-xhdpi/ic_launcher.png b/src/vi/nettruyen/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..11c615e0e
Binary files /dev/null and b/src/vi/nettruyen/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/vi/nettruyen/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/nettruyen/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..ee0a1f038
Binary files /dev/null and b/src/vi/nettruyen/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/vi/nettruyen/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/nettruyen/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..07c34f651
Binary files /dev/null and b/src/vi/nettruyen/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/vi/nettruyen/res/web_hi_res_512.png b/src/vi/nettruyen/res/web_hi_res_512.png
new file mode 100644
index 000000000..0c2cbb240
Binary files /dev/null and b/src/vi/nettruyen/res/web_hi_res_512.png differ
diff --git a/src/vi/nettruyen/src/eu/kanade/tachiyomi/extension/vi/nettruyen/NetTruyen.kt b/src/vi/nettruyen/src/eu/kanade/tachiyomi/extension/vi/nettruyen/NetTruyen.kt
new file mode 100644
index 000000000..621a68aa2
--- /dev/null
+++ b/src/vi/nettruyen/src/eu/kanade/tachiyomi/extension/vi/nettruyen/NetTruyen.kt
@@ -0,0 +1,221 @@
+package eu.kanade.tachiyomi.extension.vi.nettruyen
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.source.model.*
+import eu.kanade.tachiyomi.source.online.ParsedHttpSource
+import okhttp3.HttpUrl
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import java.util.*
+
+class NetTruyen : ParsedHttpSource() {
+
+    override val name = "NetTruyen"
+
+    override val baseUrl = "http://www.nettruyen.com"
+
+    override val lang = "vi"
+
+    override val supportsLatest = true
+
+    override val client: OkHttpClient = network.cloudflareClient
+
+    override fun popularMangaSelector() = "#ctl00_divCenter div.items div.item"
+
+    override fun latestUpdatesSelector() = popularMangaSelector()
+
+    override fun popularMangaRequest(page: Int): Request {
+        return GET("$baseUrl/tim-truyen?status=-1&sort=11&page=$page", headers)
+    }
+
+    override fun latestUpdatesRequest(page: Int): Request {
+        return GET("$baseUrl/tim-truyen?page=$page", headers)
+    }
+
+    override fun popularMangaFromElement(element: Element): SManga {
+        val manga = SManga.create()
+        element.select("a").first().let {
+            manga.setUrlWithoutDomain(it.attr("href"))
+            manga.title = it.attr("title").replace("Truyện tranh", "").trim()
+            manga.thumbnail_url = it.select("img").first()?.attr("src")
+        }
+        return manga
+    }
+
+    override fun latestUpdatesFromElement(element: Element): SManga {
+        return popularMangaFromElement(element)
+    }
+
+    override fun popularMangaNextPageSelector() = "ul a.next-page"
+
+    override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector()
+
+    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+        var url = HttpUrl.parse("$baseUrl/tim-truyen?")!!.newBuilder()
+        (if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
+            when (filter) {
+                is Genre -> {
+                    url = if (filter.state == 0) url else
+                        HttpUrl.parse(url.toString()
+                                .replace("tim-truyen?",
+                                        "tim-truyen/${getGenreList().map { it.first }[filter.state]}?"))!!
+                                .newBuilder()
+                }
+                is Status -> {
+                    url.addQueryParameter("status", if (filter.state == 0) "hajau" else filter.state.toString())
+                    url.addQueryParameter("sort", "0")
+                }
+            }
+        }
+        url.addQueryParameter("keyword", query)
+        return GET(url.toString(), headers)
+    }
+
+    override fun searchMangaSelector() = popularMangaSelector()
+
+    override fun searchMangaFromElement(element: Element): SManga {
+        return popularMangaFromElement(element)
+    }
+
+    override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
+
+    override fun mangaDetailsParse(document: Document): SManga {
+        val infoElement = document.select("#item-detail").first()
+
+        val manga = SManga.create()
+        manga.author = infoElement.select(".author a").first()?.text()
+        manga.genre = infoElement.select(".kind a").joinToString { it.text() }
+        manga.description = infoElement.select("#item-detail > div.detail-content > p").text()
+        manga.status = infoElement.select(".status > p.col-xs-8").first()?.text().orEmpty().let { parseStatus(it) }
+        manga.thumbnail_url = infoElement.select("img").attr("src")
+        return manga
+    }
+
+    private fun parseStatus(status: String) = when {
+        status.contains("Đang tiến hành") -> SManga.ONGOING
+        status.contains("Hoàn thành") -> SManga.COMPLETED
+        else -> SManga.UNKNOWN
+    }
+
+    override fun chapterListSelector() = "#nt_listchapter li:not(.heading)"
+
+    override fun chapterFromElement(element: Element): SChapter {
+        val urlElement = element.select("a").first()
+        val chapter = SChapter.create()
+        chapter.setUrlWithoutDomain(urlElement.attr("href"))
+        chapter.name = urlElement.text()
+        chapter.date_upload = element.select(".col-xs-4.text-center").last()?.text()?.let { parseChapterDate(it) } ?: 0
+        return chapter
+    }
+
+    private fun parseChapterDate(date: String): Long {
+        val dates: Calendar = Calendar.getInstance()
+        if (date.contains("/")) {
+            return if (date.contains(":")) {
+                // Format eg 17:02 20/04
+                val dateDM = date.split(" ")[1].split("/")
+                dates.set(dates.get(Calendar.YEAR), dateDM[1].toInt(), dateDM[0].toInt())
+                dates.timeInMillis
+            } else {
+                // Format eg 18/11/17
+                val dateDMY = date.split("/")
+                dates.set(2000 + dateDMY[2].toInt(), dateDMY[1].toInt(), dateDMY[0].toInt())
+                dates.timeInMillis
+            }
+        } else {
+            // Format eg 1 ngày trước
+            val dateWords: List<String> = date.split(" ")
+            if (dateWords.size == 3) {
+                val timeAgo = Integer.parseInt(dateWords[0])
+                when {
+                    dateWords[1].contains("phút") -> dates.add(Calendar.MINUTE, -timeAgo)
+                    dateWords[1].contains("giờ") -> dates.add(Calendar.HOUR_OF_DAY, -timeAgo)
+                    dateWords[1].contains("ngày") -> dates.add(Calendar.DAY_OF_YEAR, -timeAgo)
+                    dateWords[1].contains("tuần") -> dates.add(Calendar.WEEK_OF_YEAR, -timeAgo)
+                    dateWords[1].contains("tháng") -> dates.add(Calendar.MONTH, -timeAgo)
+                    dateWords[1].contains("năm") -> dates.add(Calendar.YEAR, -timeAgo)
+                }
+                return dates.timeInMillis
+            }
+        }
+        return 0L
+    }
+
+
+    override fun pageListParse(document: Document): List<Page> {
+        val pages = mutableListOf<Page>()
+        document.select(".page-chapter img").forEach {
+            pages.add(Page(pages.size, "", it.attr("src")))
+        }
+        return pages
+    }
+
+    override fun imageUrlParse(document: Document) = ""
+
+    private fun getStatusList() = arrayOf("Tất cả", "Đang tiến hành", "Đã hoàn thành", "Tạm ngừng")
+
+    private class Status(status: Array<String>) : Filter.Select<String>("Status", status)
+    private class Genre(genreList: Array<String>) : Filter.Select<String>("Thể loại", genreList)
+
+    override fun getFilterList() = FilterList(
+            Status(getStatusList()),
+            Genre(getGenreList().map { it.second }.toTypedArray())
+    )
+
+    private fun getGenreList() = arrayOf(
+            "tim-truyen" to "Tất cả",
+            "action" to "Action",
+            "adult" to "Adult",
+            "adventure" to "Adventure",
+            "anime" to "Anime",
+            "chuyen-sinh" to "Chuyển Sinh",
+            "comedy" to "Comedy",
+            "comic" to "Comic",
+            "cooking" to "Cooking",
+            "co-dai" to "Cổ Đại",
+            "doujinshi" to "Doujinshi",
+            "drama" to "Drama",
+            "dam-my" to "Đam Mỹ",
+            "ecchi" to "Ecchi",
+            "fantasy" to "Fantasy",
+            "gender-bender" to "Gender Bender",
+            "harem" to "Harem",
+            "historical" to "Historical",
+            "horror" to "Horror",
+            "josei" to "Josei",
+            "live-action" to "Live action",
+            "manga" to "Manga",
+            "manhua" to "Manhua",
+            "manhwa" to "Manhwa",
+            "martial-arts" to "Martial Arts",
+            "mature" to "Mature",
+            "mecha" to "Mecha",
+            "mystery" to "Mystery",
+            "ngon-tinh" to "Ngôn Tình",
+            "one-shot" to "One shot",
+            "psychological" to "Psychological",
+            "romance" to "Romance",
+            "school-life" to "School Life",
+            "sci-fi" to "Sci-fi",
+            "seinen" to "Seinen",
+            "shoujo" to "Shoujo",
+            "shoujo-ai" to "Shoujo Ai",
+            "shounen" to "Shounen",
+            "shounen-ai" to "Shounen Ai",
+            "slice-of-life" to "Slice of Life",
+            "smut" to "Smut",
+            "soft-yaoi" to "Soft Yaoi",
+            "soft-yuri" to "Soft Yuri",
+            "sports" to "Sports",
+            "supernatural" to "Supernatural",
+            "thieu-nhi" to "Thiếu Nhi",
+            "tragedy" to "Tragedy",
+            "trinh-tham" to "Trinh Thám",
+            "truyen-scan" to "Truyện scan",
+            "truyen-mau" to "Truyện Màu",
+            "webtoon" to "Webtoon",
+            "xuyen-khong" to "Xuyên Không"
+    )
+}