siyuan-note/siyuan · error

failed to export headers

Error message

failed to export headers: %w

What it means

When init.headers is provided to siyuan.client.fetch, the bridge uses rt.ExportTo to convert the JS headers value into a Go http.Header. If the JS value cannot be exported into that Go type, this error rejects the fetch promise. Headers must be a plain object of string keys to (string or string[]) values.

Solutions

  1. Pass a plain object: { "Authorization": "token xxx", "Content-Type": "application/json" }
  2. Convert a Headers instance: Object.fromEntries(headers.entries())
  3. Convert a Map: Object.fromEntries(map)
  4. Ensure all values are strings (or arrays of strings) and header names are strings

Example fix

// before
const h = new Headers({ "Authorization": "token xx" });
await siyuan.client.fetch("/api/x", { headers: h })

// after
await siyuan.client.fetch("/api/x", { headers: { "Authorization": "token xx" } })
Defensive patterns

Strategy: type-guard

Validate before calling

function validateHeaders(h) {
  if (h === undefined) return true;
  if (typeof h !== "object" || h === null || Array.isArray(h)) return false;
  return Object.entries(h).every(([k, v]) =>
    typeof k === "string" && (typeof v === "string" || (Array.isArray(v) && v.every((x) => typeof x === "string"))));
}

Type guard

const isPlainHeaderObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v) && !(v instanceof Headers) && !(v instanceof Map);

Try / catch

try {
  const res = await siyuan.client.fetch(path, { headers });
} catch (e) {
  if (String(e).includes("failed to export headers")) {
    headers = Object.fromEntries(
      headers instanceof Headers ? headers.entries() : Object.entries(headers).map(([k, v]) => [k, String(v)])
    );
  }
}

Prevention

When it happens

Trigger: Passing headers as a Headers instance, an array of [k,v] tuples, a Map, a function, or a string instead of a plain object; header values that are numbers/objects which Go cannot decode into http.Header.

Common situations: Reusing a browser Headers object built elsewhere; copying axios-style header arrays; passing numeric values like { "Content-Length": 123 }.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/abe0423cf7caea67. Report an issue: GitHub.

Appendix: source

Thrown at kernel/plugin/api_client.go:75

		if goja.IsString(call.Argument(0)) {
			path = call.Argument(0).String()
		} else {
			argErr = fmt.Errorf("path required")
		}
		if argErr == nil && !strings.HasPrefix(path, "/") {
			argErr = fmt.Errorf("path must start with /")
		}
		if argErr == nil {
			if init := call.Argument(1); isJsValueNotNull(init) {
				if initObj := init.ToObject(rt); initObj != nil {
					if m := initObj.Get("method"); goja.IsString(m) {
						method = m.String()
					}

					if h := initObj.Get("headers"); isJsValueNotNull(h) {
						if exportErr := rt.ExportTo(h, &headers); exportErr != nil {
							argErr = fmt.Errorf("failed to export headers: %w", exportErr)
						}
					}

					if argErr == nil {
						if b := initObj.Get("body"); isJsValueNotNull(b) {
							if goja.IsString(b) {
								bodyString = new(b.String())
							} else {
								body := b.Export()
								if arrayBuffer, ok := body.(goja.ArrayBuffer); ok {
									src := arrayBuffer.Bytes()
									bodyBytes = new(src)
								}
							}
						}
					}
				}
			}

View on GitHub (pinned to 9f775e8a12)