Skip to main content
The current version of KomentoScript is v1.
KomentoScript is in early-phase undergoing testing & can be subject to change. Documentation will be more limited during initial development & testing periods.
KomentoScript combines the logic behind custom websites, as well as allow additional more complex logic, pertaining to additional details such as dates, fallbacks, amongst other things. The syntax is inspired by similar syntax used in MAL-Sync’s ChibiScript, except more catered more towards rendering Hayami on sites itself and use of Hayami’s different rendering modes. Before getting started, fork Hayami’s KomentoScript Pages Builder if you want to host your own multi-site endpoint. When ready, read the “Quick start guide” to understand KomentoScript’s details, or find the schema attached. Hayami also accepts manually importing KomentoScript files instead of via a URL to test functionality. Primary schema: komentoscript-pack.schema.json (raw).

Quick Start

KomentoScript organises each site into a pack. A pack is one JSON object with metadata and a list of targets. An example valid KomentoScript is below.
{
  "komentoVersion": "1.0",
  "id": "hayami-example",
  "targets": [
    {
      "targetId": "example.watch",
      "match": {
        "origins": ["https://example.com"],
        "pathGlobs": ["/watch/*"]
      },
      "extract": {
        "animeTitle": {
          "selector": "h1.title",
          "attr": "text",
          "required": true
        },
        "episodeNumber": {
          "pipeline": [
            ["querySelector", ".episode"],
            ["text"],
            ["regex", "(\\d+)"],
            ["number"]
          ],
          "required": true
        }
      },
      "placement": {
        "display": "below",
        "mountSelector": "main"
      }
    }
  ]
}
This pack adds a single target for https://example.com/watch/*, extracts the anime title and episode number, and mounts the UI below the main content.

Required fields

  • Top-level: komentoVersion, id, targets[].
  • Each target: targetId, match.origins[], and extract.animeTitle + extract.episodeNumber.
If these are missing or invalid, the pack is rejected.

Schema-first field reference

Top-level:
  • komentoVersion: string
  • id: string
  • name?: string
  • updatedAt?: string
  • appliesTo?: string[]
  • tags?: string[]
  • profiles?: Record<string, { enabledTargets: string[] }>
  • targets: Target[]
Target:
  • targetId: string
  • priority?: number
  • extends?: string
  • mergeMode?: "replace" | "deep"
  • match: Match
  • extract: Extract
  • placement?: PlacementSingle | PlacementMap
  • mapping?: object
Match:
  • origins: string[] (required, non-empty)
  • pathGlobs?: string[]
  • excludePathGlobs?: string[]
Extract fields:
  • animeTitle (required)
  • episodeNumber (required)
  • episodeReleaseDate
  • anilistId
  • malId
Each extract field can use selector mode or pipeline mode.

Matching pages

"match": {
  "origins": ["https://example.com"],
  "pathGlobs": ["/watch/*"],
  "excludePathGlobs": ["/watch/trailer/*"]
}
  • origins must exactly equal location.origin (e.g. https://example.com).
  • pathGlobs and excludePathGlobs use * as a wildcard on the path.

Extracting data

Supported extract variables:
  • animeTitle (required)
  • episodeNumber (required)
  • episodeReleaseDate
  • anilistId
  • malId
You can use either simple selectors or pipelines to grab this data. Selector mode:
"animeTitle": {
  "selector": "h1.title",
  "xPath": "", // optional
  "attr": "text",
  "required": true
}
This works as follows:
  • selector is a standard CSS selector string, passed to document.querySelector.
  • xPath is used if selector is unavailable.
  • attr supports text, html, or any attribute name.
Pipeline mode:
"episodeNumber": {
  "pipeline": [
    ["querySelector", ".episode"],
    ["text"],
    ["regex", "(\\d+)"],
    ["number"]
  ],
  "required": true
}
Supported pipeline steps: querySelector, text, trim, regex, number . Pipeline mode uses a workflow-like approach. For the example above, this’d undergo the following:
  1. Find an element on the page that matches .episode
  2. Read that element’s text content
  3. Apply regex (\d+) to that text and extract the first number capture
  4. Convert the extracted value to a numeric string (sanitizes to digits/decimal, parses number, then stores as string)

Placement

"placement": {
  "display": "below",
  "mountSelector": "main",
  "anchorSelector": "", // optional, depends on mode
  "mountXPath": "", // optional
  "anchorXPath": "", // optional
  "sidePadding": 240, // optional
  "iconDisplayKind": "text", // optional: "text" | "icon"
  "iconDisplayAction": "popup", // optional: "popup" | "replace"
  "iconDisplayText": "Hayami" // optional
}
  • display: "below", "insert", "replace", "popup", or "icon".
  • mountSelector / anchorSelector choose where the UI is attached.
  • sidePadding controls horizontal padding applied to the mounted container.
If using icon mode (which is icon/text trigger):
  • iconDisplayKind controls whether icon mode uses text-based or icon-based trigger styles.
  • iconDisplayAction controls whether icon click opens popup or performs replace behavior.
  • iconDisplayText controls label text when text-based icon mode is used.
If placement is omitted, runtime falls back to popup mode. You can also define multiple placements, like so:
"placement": {
  "below": {
    "default": true,
    "anchorSelector": "main",
    "mountSelector": "main"
  },
  "popup": {}
}
Doing this will, in the above example, set below as default, and allow KomentoScript users to choose how they’d like it mounted on-site, configured by your KomentoScript.
  • For popup mode, anchorSelector is mostly irrelevant.
  • If anchorSelector is missing, runtime falls back to mountSelector, then body.
  • There is also anchorXPath if CSS selectors are unreliable.

Profiles

Profiles let one pack expose multiple target sets and choose which targets are active. Use cases:
  • Keep a stable default while shipping an experimental target in the same pack.
  • Switch between target groups for site variants without publishing separate packs.
Schema shape:
"profiles": {
  "default": { "enabledTargets": ["example.watch.stable"] },
  "experimental": { "enabledTargets": ["example.watch.stable", "example.watch.new-layout"] }
}
Behavior:
  • If an active profile id is selected, its enabledTargets list is used.
  • Otherwise, runtime falls back to profiles.default.
  • If no valid profile is present (or enabledTargets is empty), profile filtering is skipped.
  • User per-source target toggles are applied in addition to profile filtering.

More examples

Standalone JSON example files:

Icon mode with text trigger

{
  "komentoVersion": "1.0",
  "id": "example-icon-text",
  "targets": [
    {
      "targetId": "example.icon.text",
      "match": {
        "origins": ["https://example.com"],
        "pathGlobs": ["/watch/*"]
      },
      "extract": {
        "animeTitle": { "selector": "h1.title", "attr": "text" },
        "episodeNumber": { "selector": ".episode", "attr": "text" }
      },
      "placement": {
        "display": "icon",
        "mountSelector": "#player-controls",
        "iconDisplayKind": "text",
        "iconDisplayAction": "popup",
        "iconDisplayText": "Open Hayami"
      }
    }
  ]
}

Icon mode with icon trigger + replace action

{
  "komentoVersion": "1.0",
  "id": "example-icon-replace",
  "targets": [
    {
      "targetId": "example.icon.replace",
      "match": {
        "origins": ["https://example.com"],
        "pathGlobs": ["/episode/*"]
      },
      "extract": {
        "animeTitle": { "selector": "h1", "attr": "text" },
        "episodeNumber": {
          "pipeline": [["querySelector", ".ep"], ["text"], ["regex", "(\\d+)"], ["number"]]
        }
      },
      "placement": {
        "display": "icon",
        "anchorSelector": "#comments-slot",
        "mountSelector": "#comments-slot",
        "iconDisplayKind": "icon",
        "iconDisplayAction": "replace"
      }
    }
  ]
}

Placement map for user-selectable display modes

{
  "placement": {
    "below": {
      "default": true,
      "mountSelector": "main",
      "anchorSelector": "main",
      "sidePadding": 24
    },
    "icon": {
      "mountSelector": "#player-controls",
      "iconDisplayKind": "text",
      "iconDisplayAction": "popup",
      "iconDisplayText": "Discuss"
    },
    "popup": {}
  }
}

Multi-target packs

You can include multiple targets in a single pack.
{
  "komentoVersion": "1.0",
  "id": "multi-site-pack",
  "targets": [
    {
      "targetId": "example.watch",
      "match": { "origins": ["https://example.com"], "pathGlobs": ["/watch/*"] },
      "extract": { /* ... */ },
      "placement": { "display": "below", "mountSelector": "main" }
    },
    {
      "targetId": "othersite.view",
      "match": { "origins": ["https://othersite.com"], "pathGlobs": ["/view/*"] },
      "extract": { /* ... */ },
      "placement": { "display": "insert", "mountSelector": "#player" }
    }
  ]
}

Getting started with KomentoScript Pages Builder

To get started, fork the repository from GitHub. You can also one-click the Cloudflare Pages, or Netlify deployment buttons, which’ll do this & auto-deploy for you. For GitHub pages, fork the repository, and enable GitHub Pages through the repository settings, and re-run the deployment workflow through Actions. Under sites/, this contains JSON files for each site. There are sample files for Crunchyroll and HIDIVE (these are sample files unintended for production-use) that demonstrate the use of KomentoScript. Feel free to rename these files, and use the schema plus this documentation to aid scripting. To test functionality through the Hayami extension, you can import files directly via:
Hayami settings > KomentoScript > Import file
You can also validate that your files fit the schema by npm run build . If there are any errors, such as trying to put custom website configurations into KomentoScript without considering the syntax:
PS C:\Users\User\Documents\komentoscript-pages> npm run build

> komentoscript-pages@1.0.0 build
> node ./scripts/build.mjs

Validation failed:
- sites\animeWebsite.json: root must have required property 'komentoVersion'
- sites\animeWebsite.json: root must have required property 'id'
- sites\animeWebsite.json: root must have required property 'targets'
- sites\animeWebsite.json: root unsupported field 'format'.
- sites\animeWebsite.json: root unsupported field 'version'.
- sites\animeWebsite.json: root unsupported field 'exportedAt'.    
- sites\animeWebsite.json: root unsupported field 'mapping'.       
Hayami’s KomentoScript Pages Builder automatically chains all sites you’ve configured to a /all endpoint when you deploy KomentoScripts Page Builder. You can also individually go to the JSON of each site by going to /id (where id is your pack ID).