How V2 anti-hotlink works
WordPress admin → Live2D settings → Basic settings → API type decides where the model loads from and who blocks hotlinking. This page only covers the two modes for the "custom new-version model" (V2 / Cubism 4+): direct loading and host on this site.
V1 models do not go through this page. V1 models are served by the plugin's local REST endpoints (
/wp-json/live2d/v1/model/*) and don't need an extra anti-hotlink layer. Everything below is V2 only.
Option A: Direct loading (good for site owners already using object storage / CDN)
In this mode the plugin doesn't touch your modelAPI — it hands the URL directly to the browser. Hotlink protection is handled by your object storage or CDN. Three common approaches (from simple to complex):
1. Public read + Referer whitelist (simplest)
- Aliyun OSS: Bucket → Data security → Anti-hotlink, add your site's domain to the whitelist.
- Tencent COS: Bucket → Security management → Anti-hotlink, similar setup.
- How it works: the browser sends a Referer when loading the model; sources outside the whitelist get a 403.
- Limitation: someone who knows packet capture can spoof Referer to bypass it. Good enough for everyday use.
2. Wrap your own CDN, enable URL signing
Put Aliyun CDN / Tencent CDN / Cloudflare in front of OSS, enable "URL signing" or "Token validation", then fill modelAPI with the signed URL. Stricter than plain Referer, but more setup.
3. Private read + signed links (strictest)
The bucket is private; every read requires a signature. This mode cannot be configured directly in the plugin's settings page — someone who knows PHP must add code to the theme's functions.php to dynamically generate signed URLs. The plugin does not bundle SDKs for any specific cloud vendor; reasons in the FAQ.
Option B: Host on this site (recommended / good for sources that can't be hotlink-protected)
When you pick this, the model files are saved into your own WordPress site (wp-content/plugins/live-2d/model/{slug}/), and the only thing exposed to the public is an encrypted temporary signed URL:
- What visitors see in F12 is just that temporary URL — the source URL is never leaked.
- Each file gets an alias (hashed path) that nobody can guess.
- Starting May 2026, only manual download / upload by the site owner is supported — there is no longer "automatic lazy download on visitor access".
What changes in the form when "Host on this site" is selected
When this option is on, the modelAPI input auto-hides; the system forces it to your site's wp-content/plugins/live-2d/model/ root. The only thing the site owner needs to fill is the modelDir list — one slug (= subdirectory name) per line. The plugin then expands each into a full manifest URL using the fixed template {root}{slug}/{slug}.model3.json.
For example, to load haru / Pio / shizuku:
modelDir:
- haru
- Pio
- shizuku
The plugin expands them into:
https://your-site.com/wp-content/plugins/live-2d/model/haru/haru.model3.json
https://your-site.com/wp-content/plugins/live-2d/model/Pio/Pio.model3.json
https://your-site.com/wp-content/plugins/live-2d/model/shizuku/shizuku.model3.json
The actual URL a visitor receives looks like /wp-json/live2d/v2/m/{token}/{alias}?e=&s= — a temporary signed link. The source can then be 404'd or hotlink-blocked without affecting anything.
Method 1: Download from URL (model already has a public URL)
- Switch "API type" to Custom new-version model · Host on this site and save.
- A "Model hosting" panel appears below, defaulting to the Download from URL tab.
- In "Model source URL" paste the model's full public
.model3.jsonURL (e.g.https://example.com/haru/haru.model3.json) and click Download to this site. - The plugin derives a slug from the URL (e.g.
haru), immediately appends it to themodelDirlist above, and queues all model files; a backend cron pulls them intomodel/haru/. - Once every job is "Cached", the front-end uses the local copy. Remember to click "Save settings" at the bottom of the page so
modelDiris written to the database.
Method 2: Upload a folder (model only exists locally)
- Switch "API type" to Custom new-version model · Host on this site and save.
- In the "Model hosting" panel switch to the Upload folder tab. Drag the entire model directory (including
.model3.json+.moc3+ textures + motions) into the dashed box, or click Choose folder to pick a directory. - The plugin uploads every file in series to
model/{slug}/(slug auto-derived from the dragged-in root directory name); a progress bar shows uploaded bytes in real time. - After upload, the slug is auto-appended to the
modelDirlist above. - The yellow banner at the top shows the server's current single-file upload limit (set by PHP
upload_max_filesize). Files over the limit are marked red and skipped — the PHP backend won't silently drop them. If your model textures are generally too big, contact your host or raiseupload_max_filesizeandpost_max_sizeinwp-config.php/.htaccess. - After uploading, click "Save settings" at the bottom so
modelDiris written to the database.
Typical scenarios
- Models hosted on GitHub Pages / GitHub Raw / jsDelivr.
- Models hosted on a friend's server where you can't ask them to add a Referer whitelist.
- Third-party paid models where the seller only gives you a public URL and you don't want to be hotlinked.
- Worried about object-storage "pay per traffic" bills going out of control.
- Models exist only locally (no public URL) — just drag and drop.
FAQ
Why doesn't the plugin connect to OSS private read for me?
People often ask "why can't the plugin take my OSS AccessKey and sign requests on my behalf?" Three reasons:
- AccessKey must not live in the database. WordPress option entries are stored in plaintext. If the database leaks (hack, backup leak, shared-host incident), your entire OSS bucket is gone — far worse than someone hotlinking a model.
- Every cloud signs differently. Aliyun, Tencent, AWS, Cloudflare R2, MinIO — each has its own scheme. Done one and someone immediately asks "add XX cloud", endless.
- People who actually need this don't need the plugin to help. A site owner who can manage OSS private read can write a few lines of PHP much faster than clicking through a settings page — and it's safer (credentials can come from environment variables instead of the database).
I want to write code to do OSS-signed links myself
The plugin does not currently provide a hook for you to intercept this flow. If you really want to do it, fork the repo and edit get_asset() / stream_local() in src/live2d-V2Api.php so that hits on your private OSS path return a 302 redirect to a pre-signed OSS URL.
If enough people ask for it, we may add a formal live2d_v2_resolve_alias hook later, but the plugin will never bundle an SDK for any specific cloud vendor.
If you're going down this path, please:
- Use a least-privilege sub-account (only "Read object", scoped to a specific bucket / directory).
- Never write the SecretKey directly into a WordPress setting — use environment variables or a dedicated secret manager.
- Add Referer validation to the V2 anti-hotlink REST path (enable explicitly in
wp-config.php; see the comments inlive2d-V2Api.php).