siyuan-note/siyuan · error

failed to export headers: %w

Error message

failed to export headers: %w

What it means

Thrown when the headers field of the fetch init object cannot be exported to map[string]string via goja's ExportTo. fetch requires headers as a flat string-to-string map; anything goja cannot coerce into that shape (arrays of pairs, nested objects, a Headers instance, non-string scalars) fails the export at api_client.go:74.

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 251596fc0d)

Solutions

  1. Pass headers as a plain object of string keys to string values: { 'Content-Type': 'application/json' }.
  2. If you have an array of pairs, convert first: Object.fromEntries(pairs).
  3. Stringify any non-string header value before putting it in the map.

Example fix

// before
await siyuan.client.fetch('/api/x', { headers: [['Content-Type','application/json']] });
// after
await siyuan.client.fetch('/api/x', { headers: { 'Content-Type': 'application/json' } });
Defensive patterns

Strategy: type-guard

Validate before calling

function normalizeHeaders(h) {
  if (h == null) return {};
  const out = {};
  for (const [k, v] of Object.entries(h)) out[String(k)] = String(v);
  return out;
}
await siyuan.client.fetch('/api/x', { headers: normalizeHeaders(init.headers) });

Type guard

const isStringMap = (v) => v != null && typeof v === 'object' && !Array.isArray(v) && Object.values(v).every(x => typeof x === 'string');

Prevention

When it happens

Trigger: Passing headers as an array of pairs ([['Content-Type','application/json']]) like the browser Headers constructor, a nested object ({ a: { b: 'c' } }), a number-valued header, or an actual fetch Headers object.

Common situations: Developer ports browser fetch code that used new Headers(...) or an entries array, or sets a header to a number expecting automatic coercion (goja's ExportTo to map[string]string does not coerce).

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/abe0423cf7caea67. Report an issue: GitHub.