tokenSecurityKey is set.
payload.config.ts
Signed URLs secure downloads (how files are served). For securing uploads — minting short-lived URLs so the browser can send files straight to Bunny — see Client uploads.
Options
Getting your token security keys
- Storage
- Stream
Bunny dashboard → Delivery → CDN → your Pull Zone → Security → Token Authentication → enable it and
copy the key.
Requirements
- If
signedUrlsis enabled anywherestorageis configured,storage.tokenSecurityKeyis required. - If
signedUrlsis enabled anywherestreamis configured,stream.tokenSecurityKeyis required.
The
tokenSecurityKey is per zone / per library. When a collection points at its own storage zone or stream library, that zone/library must carry its own tokenSecurityKey for signed URLs to work on it — the global key isn’t inherited.A Bunny token covers the query string, so any parameter added after signing invalidates it. Payload’s
upload.cacheTags appends the document’s updatedAt to admin thumbnails, which would break those links — the plugin therefore sets cacheTags: false on collections that hand signed CDN URLs straight to the browser (disablePayloadAccessControl: true). Filenames are already unique per upload, so cache busting isn’t needed there.Proxy vs. redirect (staticHandler)
When a collection uses Payload’s access control (the default), a file request goes through Payload first. staticHandler controls what happens next:
IP locking (userIp)
userIp binds each signed link to a single client IPv4 address — a leaked URL stops working from any other IP. You provide the callback because reliably extracting the real client IP depends on your host and proxy chain:
- The IP goes into the token hash only — it is never added to the URL.
- Return a falsy value when no IP can be determined; the link is then signed without an IP lock rather than failing the request.
- The callback runs only for URLs the client itself will open — document
urlfields, admin thumbnails, andstaticHandlerredirects. URLs your server fetches on the client’s behalf (proxied downloads) and URLs generated outside a request (cache purging) are never IP-locked, since Bunny would see your server’s IP there, not the client’s.
Clients behind carrier-grade NAT or switching networks (mobile) can legitimately change IPs mid-session, which breaks IP-locked links. Prefer short
expiresIn values over IP locking for those audiences, or combine both with a tolerant userIp callback.Absolute expiry (expiresAt)
expiresIn is a sliding window from the moment each URL is signed. expiresAt sets a fixed cut-off instead — useful when links must all die at the same wall-clock moment, such as the end of a live event or a rental period:
Date or a UNIX timestamp in seconds. Return undefined to fall back to expiresIn for that URL. The callback receives the same { collection, filename, req? } args as shouldUseSignedUrl.