Writing guide
Scripts published here install in Ikun Browser. The client is close to a userscript manager, but not a full desktop one. Check the rules below before publishing so installs and updates do not fail.
The site language is a directory in the URL, such as /zh-CN/, /zh-TW/, /ja/, /ko/, /id/, and /en/. Lists show scripts in that language by default. The install URL /s/id.user.js has no language directory.
Publishing
- Sign up and log in.
- Open Publish and paste the full source. It must include an ==UserScript== header.
- To update, open Publish a new version on that script and paste the new source. Do not change the script ID, or installed users will not get the update.
At most 50 scripts per person. Once unlisted, the install link returns 404 and the script disappears from search.
Smallest working script
// ==UserScript==
// @name Example script
// @namespace https://scripts.ikunbrowser.com
// @version 1.0.0
// @description Add a line on the example page
// @match https://example.com/*
// @grant none
// ==/UserScript==
(function () {
document.body.insertAdjacentHTML("beforeend", "<p>Example script ran</p>");
})();
Metadata
You need @name, plus @match or @include.
| Field | Notes |
|---|---|
@name | A name without a language suffix wins. If there are only localized names such as @name:zh-CN, the first one is used. |
@name:zh-CN @description:ja | A name and description for one language. That directory shows them first. If there is no plain @name, the first localized name decides the script language. |
@namespace @version @description | Write all of them. @description:xx is used only when there is no default description. |
@match | Chrome match patterns. *://, *.example.com, and <all_urls> work. The site page lists scripts by the domains in these rules, for example /scripts/by-site/example.com. |
@include @exclude | Wildcards * and ?, or /regex/i. |
@exclude-match | Exclude with match rules. |
@run-at | document-start / document-end / document-body / document-idle. Omitted means idle. |
@noframes | Does not run in subframes. |
@grant | Unknown grants do not block installation. Unimplemented functions exist and do nothing. |
@require @resource | http or https only, at most 8 each. data:, file:, and relative paths fail to install. |
@connect @icon @author | Ignored by the client and does not affect installation. |
At most 64 match rules and 64 exclude rules. Each @require is at most 512KB, about 1MB combined. Each @resource is at most 256KB.
Updates
The client does not compare semantic versions. After trimming, the new @version string must differ from the current one. The script is replaced only when the user checks for updates manually. Two empty versions also count as unchanged.
When serving /s/{id}.user.js, this site inserts @downloadURL and @updateURL at the start of the metadata block, both pointing at that URL. The client reads only the first one, so an update URL you write later is ignored. Do not point @updateURL at .meta.js. The client skips that kind of URL.
The browser does not update in the background. Users check manually in Settings → Userscripts.
Install link
The Install button on the detail page is a normal .user.js link. Ikun Browser intercepts any http(s) URL ending in .user.js and shows a confirmation. No extra protocol is required.
Greasy Fork style URLs also work: /scripts/id.user.js, /scripts/id.meta.js, /scripts/id/code/name.user.js, the /scripts/id-name page, and /scripts/id.json. The update URL given to Ikun Browser stays /s/id.user.js.
The same script, day, and IP counts as one install.
APIs the client supports
Both synchronous GM_* and Promise-style GM.* are injected. The script runs in the page context. unsafeWindow is the page window. There is no separate sandbox.
| API | Behavior |
|---|---|
GM_addStyle GM_addElement | Insert a style or an element. |
GM_getValue | Stored locally and isolated per script. One value is at most 32KB. Change listeners stay on the current page and do not cross tabs. |
GM_xmlhttpRequest | Supports common methods plus arraybuffer / blob. Request bodies are at most 256KB and responses at most 1MB. Public http(s) only; local addresses are rejected. It does not go through page JS or the browser proxy. |
GM_setClipboard GM_download GM_openInTab | Clipboard, in-app download confirmation, and opening http(s) in a new tab. |
GM_getResourceText GM_getResourceURL | Read resources cached at install time. |
GM_registerMenuCommand | If the current page registers a command, the bottom bar shows Script commands. |
GM_notification | Shows a toast only. There is no click callback. |
GM_getTab | Only an in-memory object in the current script process, not multiple windows. |
These still install if you @grant them, but the functions do nothing: GM_cookie, GM_webRequest, and any grant not listed above. Do not rely on rewriting requests or reading and writing cookies.
Runtime notes
- Source is at most 512KB. At most 64 scripts can be installed.
- If the page CSP blocks unsafe-eval, the script fails silently.
- On a single-page app navigation, a newly matched script that has not run yet will run. One that already ran is not run again.
- document-start is complete in a newly opened tab. The page already open right after install may miss the real start of the document.
- The home page and about: are not injected. Script errors are swallowed, and the page does not show a userscript-manager log.
What kind of script fits
A good fit: change the DOM, add CSS, store a little config, make occasional requests, use the clipboard, download a file, toggle from a menu, and load something like jQuery with @require.
A poor fit: heavy Cookie use, intercepting network requests, needing the exact start of the document, a strict page CSP, or a very large script and dependencies.