{"record":{"id":"e4483ca46305a39a","repo":"denoland/deno","slug":"unsupported-exports-shape-in-deno-json-expected","errorCode":null,"errorMessage":"unsupported 'exports' shape in deno.json: expected a string or object, got {}","messagePattern":"unsupported 'exports' shape in deno\\.json: expected a string or object, got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/tools/pack/package_json.rs","lineNumber":180,"sourceCode":"              // omit `types` when no .d.ts was generated\n            } else {\n              rewritten.insert(\n                condition.clone(),\n                json!(format!(\"./{}\", ts_to_js_extension(path))),\n              );\n            }\n          } else {\n            rewritten.insert(condition.clone(), cond_value.clone());\n          }\n        }\n        result.insert(key.clone(), serde_json::Value::Object(rewritten));\n      }\n    }\n\n    return Ok(serde_json::Value::Object(result));\n  }\n\n  deno_core::anyhow::bail!(\n    \"unsupported 'exports' shape in deno.json: expected a string or object, got {}\",\n    exports\n  )\n}\n\nfn extract_main_and_types(\n  exports: &Option<serde_json::Value>,\n  dts_set: &HashSet<String>,\n) -> (Option<String>, Option<String>) {\n  let Some(exports) = exports else {\n    let types = if dts_set.contains(\"mod.js\") {\n      Some(\"./mod.d.ts\".to_string())\n    } else {\n      None\n    };\n    return (Some(\"./mod.js\".to_string()), types);\n  };\n","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/cli/tools/pack/package_json.rs#L162-L198","documentation":"While translating a deno.json into package.json metadata for packing, the exports rewriter only accepts a string or a plain object. Any other JSON type (array, number, boolean, null) falls through to this bail with the offending value printed.","triggerScenarios":"deno.json `\"exports\"` set to an array like `[\"./mod.ts\"]`, a boolean, a number, or otherwise non-string/non-object JSON — including malformed nesting from hand edits.","commonSituations":"Copying an npm package.json `exports` array shape (npm allows arrays, deno.json does not);. Editing deno.json by hand and leaving a trailing value or wrong-type placeholder. Tooling that serializes exports from a list without converting to an object.","solutions":["Make `exports` a string (`\"exports\": \"./mod.ts\"`) or a conditions object (`{\"exports\": {\".\": \"./mod.ts\"}}`).","If you copied from package.json, convert array forms to their first entry or an object mapping.","Validate the config with `deno publish --dry-run` or a JSON schema before packing."],"exampleFix":"// deno.json (before)\n{\n  \"exports\": [\"./mod.ts\", \"./lib.ts\"]\n}\n// deno.json (after)\n{\n  \"exports\": { \".\": \"./mod.ts\", \"./lib\": \"./lib.ts\" }\n}","handlingStrategy":"type-guard","validationCode":"const cfg = JSON.parse(readFileSync(\"deno.json\", \"utf8\"));\nconst e = cfg.exports;\nif (typeof e !== \"string\" && (typeof e !== \"object\" || e === null || Array.isArray(e))) {\n  console.error(\"deno.json 'exports' must be a string or an object\");\n  process.exit(1);\n}","typeGuard":"const isValidExports = (e: unknown): e is string | Record<string, unknown> =>\n  typeof e === \"string\" || (typeof e === \"object\" && e !== null && !Array.isArray(e));","tryCatchPattern":null,"preventionTips":["Never copy npm's array form of `exports` into deno.json.","Use the deno.json JSON schema in editors to get type hints for `exports`.","Run `deno publish --dry-run` after editing exports."],"tags":["pack","deno-json","exports","package-json"],"backgroundTag":"invalid-package-exports","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}