In short
- A source is one JSON file that describes four requests — search, series details, chapters, pages — and how to read their answers. It can’t run code.
- A repository is a signed list of sources, hosted as static files on any HTTPS site you control.
- One tool creates the project, tests it against the real site, signs everything and builds the files to upload.
Before you start
You’ll need:
- a Mac with macOS 14 or later, and the Kiyume source tool (6 MB, Apple Silicon and Intel). No Xcode needed. Unzip it and install it from Terminal:
Thecd ~/Downloads/kiyume-source-tool xattr -d com.apple.quarantine source-validator sudo mkdir -p /usr/local/bin sudo mv source-validator /usr/local/bin/xattrline is there because the tool isn’t notarised by Apple yet: without it, macOS refuses to open a program downloaded from the web. - static hosting with HTTPS for your repository: any web host, or a service such as GitHub Pages;
- the address of the site you want to read, and the right to publish an adapter for it. Read its terms first.
A source contains no manga. It doesn’t add screens to Kiyume and can’t reach the network, files or the device on its own: Kiyume does the requests for it, only to the addresses it declares, over HTTPS.
Pick the simplest kind
- The site has a JSON APIUse a JSON source. It’s the most stable kind, because APIs change less often than page layouts.
- The site only returns web pagesUse an HTML source, which reads the pages with CSS selectors.
- It’s your own server at homeDon’t publish a repository for it. Connect it directly in Kiyume instead: it supports Komga.
Create the project
Start from the site’s address. For a site that returns web pages:
source-validator init https://examplescans.com my-source --name "Example Scans" --developer "Your name" --language en
For a site with a JSON API, add --format json. The command creates a folder with:
| File | What it is |
|---|---|
manifest.json | Who made the source, what it reads, and what it’s allowed to reach. |
source.json | The definition: the four requests and how to read them. |
kiyume-publish.json | Settings for publishing your repository. |
private/ | Two private keys, one for the source and one for the repository. Already excluded by .gitignore. |
README.md | Next steps, including a ready-made brief for an AI coding assistant. |
It never writes into a folder that already has files in it. The generated definition is a valid starting shape, not a working source: no tool can tell from an address alone which selectors or fields a site uses.
The manifest
{
"manifestVersion": 1,
"id": "en.examplescans",
"name": "Example Scans",
"developer": "Your name",
"version": "0.1.0",
"apiVersion": "1.0.0",
"minAppVersion": "1.0.0",
"languages": ["en"],
"content": { "rating": "teen", "minimumAge": 13 },
"allowedDomains": ["examplescans.com"],
"capabilities": ["network"],
"implementation": "declarative",
"homepageURL": "https://examplescans.com",
"sourceURL": "https://examplescans.com"
}
id— language, then site:en.examplescans,it.esempio,multi.example. Lowercase letters, numbers, dots and dashes.version— raise it for every update you publish. A published version can never be replaced.languages— the languages of the catalogue, as language codes.content—ratingiseveryone,teen,matureoradult, with an optionalminimumAgeanddescriptorssuch asviolenceornudity. An adult catalogue must be ratedadultand 18+. Review the starting value: it’steenonly because a manifest can’t publish an unknown rating.allowedDomains— every host the source will contact, including the one serving images. Kiyume refuses any other address.minAppVersion— the oldest Kiyume that can read your definition. Raise it when you use a newer feature, or older versions will install the source and fail.
The signing fields, integrityHash and signingIdentity, are filled in by the tool when it signs the package. Don’t edit them by hand.
The definition
A definition describes where to send four requests and how to read the answers. It can’t loop, branch, or run code: everything in it is data Kiyume interprets.
{
"schemaVersion": 1,
"search": {
"endpoint": { "url": "https://examplescans.com/search", "query": { "q": "{query}" } },
"format": "html", "records": "article.manga",
"manga": {
"id": { "attribute": "data-id" },
"title": { "path": "h2" },
"coverURL": { "path": "img", "attribute": "src" }
},
"nextPage": "a.next"
},
"manga": {
"endpoint": { "url": "https://examplescans.com/manga/{id}" },
"format": "html", "record": "main.manga",
"manga": {
"id": { "attribute": "data-id" },
"title": { "path": "h1" },
"authors": { "path": ".author" },
"description": { "path": ".description" },
"status": { "path": ".status" }
}
},
"chapters": {
"endpoint": { "url": "https://examplescans.com/manga/{mangaID}/chapters" },
"format": "html", "records": "li.chapter",
"chapter": {
"id": { "attribute": "data-id" },
"title": { "path": ".title" },
"chapterNumber": { "attribute": "data-number" },
"language": { "constant": "en" }
}
},
"pages": {
"endpoint": { "url": "https://examplescans.com/chapter/{chapterID}" },
"format": "html", "records": "img.page",
"page": { "imageURL": { "attribute": "src" } }
}
}
Each request can use these values in {braces}:
| Request | Values |
|---|---|
search | {query}, {page}, {offset}, {filter_<id>} |
latest (optional) | {page}, {offset} |
manga | {id} |
chapters | {mangaID} |
pages | {chapterID} |
{offset} exists only when the request declares pageSize, and equals (page − 1) × pageSize: many APIs count records rather than pages.
Declare in schemaVersion the version of the features you use: 4 for path filters, alternatives and the newer transforms, 5 for collections, 6 for chapter lists spread over several pages. A feature used under a lower version is rejected.
Reading the response
HTML is read with CSS selectors. JSON is read with a dotted path, with a few extra forms:
| Path | Means |
|---|---|
attributes.title.en | Plain keys, and array positions by number. |
relationships[type=author].attributes.name | Every array element whose field has that value. |
attributes.tags[].attributes.name.en | Every element of an array. |
attributes.title.* | Any one value of an object — useful when titles are keyed by language. |
Each field reads from exactly one of path (the record), constant (a fixed text), valueName (a value from an earlier request), parts (several rules joined) or alternatives (the first rule that finds something).
Then transforms run in order: trim, lowercase, uppercase, prefix, suffix, replaceLiteral, split, join, first, last, extractNumber, mapValue, mapAny. extractNumber turns “Chapter 12.5” into 12.5; mapValue turns the site’s words into Kiyume’s, like “Completed” into completed.
When a page list needs something from another page first — a host name, a chapter key — use up to three preflight requests before the final one, and read their values with valueName.
Fields worth the effort
chapter.externalURL— for licensed series the site links to rather than hosts. Kiyume then opens the official release instead of showing a chapter that can’t load.chapter.publishedAtwithpublishedAtDate— date formats tried in order:iso8601,unix, or a date pattern with itslocale, since month names change with the language.manga.viewerHint—rightToLeft,leftToRightorverticalContinuous. Kiyume trusts it over guessing from tags.manga.publicationType—manga,manhwa,manhua,webtoon,oneShotand more. Map it in search results too: the reader needs it before the first chapter opens.filters— text, single choice or on/off controls, offered in Kiyume before a search. Their values arrive as{filter_<id>}.collections— up to eight shelves such as “Popular” or “Recently updated”, each one the search seen through fixed filter values. Declare a shelf only if the site can really answer it.
Test it
source-validator definition source.json
source-validator manifest manifest.json
source-validator definition-live-health manifest.json source.json "a title to search"
The first two commands check the files. The third runs the source against the real site, with the same limits Kiyume applies, and checks search, series details, chapters, pages and the first image.
Publish a repository
A repository can hold as many sources as you like. It’s only a signed list: readers add it once, see your sources with their logos, and choose which to install.
If you started with init, your keys and kiyume-publish.json already exist. Otherwise, create two different keys once, and keep private/ out of version control:
mkdir -p private
source-validator key-generate private/source.key
source-validator key-generate private/repository.key
Then fill in kiyume-publish.json — above all your public HTTPS address and a contact for reports — and add a square icon.png for the logo:
{
"schemaVersion": 1,
"publicBaseURL": "https://sources.example.com/",
"outputDirectory": "public",
"repository": {
"id": "example.sources",
"operatorName": "Example Sources",
"keyID": "example-repository-key",
"privateKey": "private/repository.key",
"allowedDomains": ["sources.example.com"],
"feedbackURL": "mailto:sources@example.com"
},
"source": {
"manifest": "manifest.json",
"payload": "source.json",
"privateKey": "private/source.key",
"icon": "icon.png",
"filters": null
}
}
source-validator publish kiyume-publish.json
The command checks both files, signs and verifies the source package, creates or updates the signed list without dropping earlier versions, and builds a small web page for your repository. It refuses to overwrite a different package with the same name and version.
Upload only the folder named in outputDirectory. Never upload, commit or share the private keys. To publish an update, raise version in the manifest and run the same command again.
Share it
The generated page has two buttons, Add repository to Kiyume and Install source. On a device with Kiyume, they open a preview with your name, the addresses your sources may contact and your key’s fingerprint. Nothing is ever installed without the reader confirming.
Publish your repository key’s fingerprint somewhere readers already trust — your site, your profile, your project page — so they can check it matches what Kiyume shows.
Limits
- HTTPS only, and only the domains in the manifest, whatever the definition asks for.
- No loops, no conditions, no arithmetic beyond
{offset}. - Paths up to 32 steps, 8 transforms per field, 3 preflight requests per operation.
- A chapter list is one request, unless it declares
pageSizeunder schema 6: then up to 20 pages are read. - Packages up to 10 MB, and responses up to 8 MB.
Ground rules
A repository should host only its signed list, the signed sources, their logos, an information page and a way to reach you — never pages, chapters, copied covers, credentials or mirrors of a site. Rate content honestly, answer reports and remove a contested source promptly. The full list is on the Sources page.
Signing and Kiyume’s limits stop a source from misbehaving technically. They don’t decide whether an adapter is allowed by a site’s terms or by the law: that’s up to you. This guide isn’t legal advice.
Questions about the format? Write to support@kiyume.app.