siyuan-note/siyuan · error
invalid plugin JSONP response
Error message
invalid plugin JSONP response
What it means
For endpoints declared with PluginServiceJSONP, ValidatePluginServiceResponse requires the payload to be a JSONP envelope: either a bare JSON value or a JavaScript-style call like callback({...}); (a trailing ');' is tolerated). The validator scans for a '(' and checks that everything after it parses as JSON; if neither form is valid it returns "invalid plugin JSONP response". This enforces that the JSONP contract is met so browsers can execute the wrapper as a function call.
Solutions
- Marshal the inner payload to valid JSON first, then wrap it as callbackName(<json>) (optionally with a trailing semicolon)
- Validate the inner JSON with json.Valid before wrapping it in the callback
- Check that the response is not an error page or empty body being validated as JSONP
- Return the endpoint in a JSON mode instead of JSONP if no client-side callback wrapper is actually needed
Example fix
// before fmt.Fprintf(w, "%s(%s)", callback, rawBody) // after inner, _ := json.Marshal(data) fmt.Fprintf(w, "%s(%s);", callback, inner)
Defensive patterns
Strategy: validation
Validate before calling
func isValidJSONP(payload []byte) bool {
s := string(payload)
if json.Valid(payload) { return true }
if tail, ok := strings.CutSuffix(s, ");"); ok {
for i, c := range tail { if c == '(' && json.Valid([]byte(tail[i+1:])) { return true } }
}
return false
} Try / catch
if err := bundle.ValidatePluginServiceResponse(method, path, PluginServiceJSONP, status, ct, payload); err != nil { if strings.Contains(err.Error(), "invalid plugin JSONP response") { rewrapAsJSONPAndRetry(); return }; return err } Prevention
- Build JSONP bodies by marshaling the inner value first, then wrapping in callback(...)
- Validate the inner JSON with json.Valid before wrapping
- Do not emit JSONP for error pages; return an HTTP error status instead
- Test the exact bytes your handler emits for JSONP endpoints
When it happens
Trigger: Calling Bundle.ValidatePluginServiceResponse with mode PluginServiceJSONP and a payload that is neither valid JSON nor of the form prefix(json)[;] — e.g. an empty string, plain text, or a callback wrapper whose inner payload is malformed JSON.
Common situations: The plugin handler wraps a non-JSON body in the callback name; the inner JSON was truncated or produced by string concatenation; the response is plain HTML from an error page; the callback name itself contains characters that break the parenthesis scan.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- invalid plugin JSON response
- invalid plugin secure JSON response
- builtin color must not be null
- builtin style must not be null
- Conf.Language(317)
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/7e3a6007e63da937.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/plugin_service_protocol.go:222
if len(payload) != 0 {
return fmt.Errorf("empty plugin response contains a body")
}
case PluginServiceJSON, PluginServiceASCIIJSON, PluginServiceIndentedJSON, PluginServicePureJSON:
if !json.Valid(payload) {
return fmt.Errorf("invalid plugin JSON response")
}
case PluginServiceJSONP:
valid := json.Valid(payload)
if tail, ok := strings.CutSuffix(string(payload), ");"); ok {
for index, char := range tail {
if char == '(' && json.Valid([]byte(tail[index+1:])) {
valid = true
break
}
}
}
if !valid {
return fmt.Errorf("invalid plugin JSONP response")
}
case PluginServiceSecureJSON:
if !json.Valid(payload) && !json.Valid([]byte(strings.TrimPrefix(string(payload), "while(1);"))) {
return fmt.Errorf("invalid plugin secure JSON response")
}
case PluginServiceWebSocket:
if status == 101 && len(payload) != 0 {
return fmt.Errorf("WebSocket handshake contains a body")
}
case PluginServiceXML:
decoder := xml.NewDecoder(strings.NewReader(string(payload)))
for {
if _, err := decoder.Token(); err != nil {
if err == io.EOF {
break
}
return err
}View on GitHub (pinned to 9f775e8a12)