Skip to main content

External metadata & import

External Metadata​

External metadata uses three settings:

  1. externalRef fields store links/ids to providers.
  2. externalFields map provider metadata into local fields.
  3. bodySections of kind: external map provider metadata into Markdown body sections.

Each provider exposes one search entry point that either resolves a pasted URL/id it recognizes or runs a free-text query. Providers that only resolve URLs/ids (no catalog search API) return nothing for free-text and resolve when handed their URL/id — the catalog's searchSupported flag tells clients which is which. The provider list, each provider's fields/types, and its credential requirements all live in the Rust core and are exposed via /api/external/providers; clients render from that rather than hard-coding.

The full provider catalog — every provider with its externalRef id, searchability, episode/track sync support, credentials (including each credential's exact KIZUNASHELF_* env var), externalTypes values, and mappable fields — is the External providers page, generated directly from the app so it always matches the version you're running.

Credentials are supplied per runtime: the web app reads them from KIZUNASHELF_* environment variables (listed per provider on the External providers page; the var name is KIZUNASHELF_<UPPER_KEY> for each catalog credential key); the desktop and iOS apps store them in the OS keychain (entered under Settings → Provider Credentials, which renders its fields from the provider catalog). Credential caveats worth knowing:

  • Discogs accepts a personal access token or an app consumer key + secret (either works; the token wins when both are set). The personal token grants access to its owner's account — prefer the consumer pair for anything shared.
  • Hardcover's token is a personal account token (the full Bearer … value) — it can read and mutate its owner's Hardcover data, so it's per-user only, never bundled or distributed.
  • Comic Vine keys are read-only but rate-limited per key — per-user only.
  • Google Books works keyless in principle, but keyless access shares an exhausted quota and returns 429 — a key is effectively required.
  • BoardGameGeek requires a registered application token — BGG returns 401 without one (register at boardgamegeek.com/using_the_xml_api).
externalPriority:
- bangumi
- igdb

fields:
- field: bgm_url
fieldType: externalRef
displayName: BGM
externalRef: bangumi
externalTypes:
- "2"

- field: title_en
fieldType: title
displayName: Title (English)
titleLanguage: en
externalFields:
- source: igdb
field: name

externalPriority​

At the type level, externalPriority controls provider order in external match/search workflows. Providers not configured through an externalRef field or an external bodySections entry are ignored.

externalRef​

externalRef declares which provider a field represents. The field value is stored in Markdown frontmatter, usually as a URL.

externalTypes is provider-specific. For example, a provider may use numeric or string type ids to distinguish anime, games, shows, or other records.

externalFields​

externalFields maps provider result metadata into local frontmatter fields. This is how "apply selected metadata" knows where to place values.

For example:

- field: cover_url
fieldType: image
displayName: Cover
externalFields:
- source: bangumi
field: cover_url
- source: igdb
field: cover_url

bodySections​

bodySections declares named sections of an entity's Markdown body, each addressed by its heading. A section's kind chooses its behavior.

KeyRequiredTypeDescription
headingyesstringThe Markdown heading (text only) the section lives under.
kindyesexternal | episodesWhat the section is.
externalFieldsfor external{ source, field }[]Provider fields that fill this heading on match. One heading can list multiple sources — the matched candidate's provider is chosen (like a field's externalFields).
trackingfor episodeschecklist | noneHow watch/read state is tracked. Default checklist.
bodySections:
# External-metadata section: "Summary" filled from whichever provider matched.
- heading: Summary
kind: external
externalFields:
- { source: bangumi, field: summary }
- { source: thetvdb, field: overview }
# Episodes section: an ordered, checkable list under "Episodes".
- heading: Episodes
kind: episodes
tracking: checklist

When an external section is applied, KizunaShelf replaces the matching heading section if it exists, else appends one; other body content is preserved.

Episodes / tracks / chapters​

An episodes body section is a Markdown list, optionally grouped by season or disc subheadings. With tracking: checklist, task checkboxes record completed items and contribute to the watched/total count. With tracking: none, the section remains a plain list. Item numbers are part of each line's text, so specials such as 12.5 are supported.

The app preserves watched marks and hand-added items when merging selected provider episodes. See Managing episodes & tracks for the editing and sync workflow, and the provider catalog for supported sources.

Quick Capture and Import​

Quick Capture​

Quick Capture creates one entity from a provider result. The core applies the type's field and body mappings; cover and episode downloads are best-effort. Existing-entity detection uses external references and title matching.

Import​

Batch import creates entities from a collection after review. It uses the same existing-entity detection as Quick Capture. The source requirements and schema mappings are listed below.

Profile imports support public profiles only; CSV sources use the file supplied by the user. Each source resolves its items to one of the built-in providers, so a type must declare an externalRef field for that provider to receive them (e.g. an externalRef: myanimelist field to import MyAnimeList/AniList/Kitsu, externalRef: tmdb for Trakt/IMDb, externalRef: steam for Steam, externalRef: openlibrary for Goodreads, externalRef: bangumi for Bangumi). The item's provider type (anime, movie, game, …) is matched against that field's externalTypes.

SourceInputResolves toCredential
Bangumiusernamebangumi—
MyAnimeListusernamemyanimelistMyAnimeList client id (same as search)
AniListusernamemyanimelist (via each entry's MAL id)—
Kitsuusernamemyanimelist (via each entry's MAL mapping)—
Traktusername (slug)tmdbTrakt client id
SteamSteamID64steamSteam Web API key
IMDbCSV exporttmdb (via /find)TMDB API key
GoodreadsCSV exportopenlibrary (via ISBN)—
YamtrackCSV exportmyanimelist, tmdb—

Import credentials are supplied like provider credentials — KIZUNASHELF_* env vars on web, the OS keychain on desktop/iOS (keys trakt_client_id, steam_api_key). A source with a missing required credential is shown but disabled, with the reason.

Your data maps through schema roles, not field names. For each imported item, the source's status is translated to a canonical (planning/ongoing/paused/completed/dropped) and written to the type's enumRole: status field via its statusValues; the score goes to the first rating field (normalized to 0–10); started/finished dates go to the dateRole started/completed fields; notes become an unmanaged ## Notes body section; and watched progress ticks the first N items of the episodes section. A role you haven't wired is simply skipped. Which of these run is controlled by per-import toggles (import user data, import episodes, mark progress).

Imported image fields keep remote URLs. See Downloading covers locally for the separate download workflow.