{"_id":"@brand-map/admin-extension-sdk","_rev":"11-5d0f1f572956171c66b57e31f818d360","name":"@brand-map/admin-extension-sdk","dist-tags":{"next":"0.0.10-alpha.4","latest":"0.0.10-alpha.34"},"versions":{"0.0.10-alpha.4":{"name":"@brand-map/admin-extension-sdk","version":"0.0.10-alpha.4","_id":"@brand-map/admin-extension-sdk@0.0.10-alpha.4","maintainers":[{"name":"alexvyber","email":"alexvyber@gmail.com"}],"dist":{"shasum":"df4c15a9065dabcdd8d40f1f8636b1fac648969a","tarball":"https://registry.npmjs.org/@brand-map/admin-extension-sdk/-/admin-extension-sdk-0.0.10-alpha.4.tgz","fileCount":6,"integrity":"sha512-v2OVwETCOaGfAR4N6yoZwj0czJuoarxfN4hQbmEcVdIIrfPMoY+s05/LYR7GQmZquSFUCAtlw88D9oHGNeaueA==","signatures":[{"sig":"MEYCIQDFUZi/YzPLya+mbVDDU634hD/XgAuw1UZwL+nYfnlDxAIhAMidQC581HZgo0fD/tB2zk+w0vUyO46eY86sCgy0RJKR","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":24250},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"6e23178105bce90071ebdbb7a9a74511d3f94ac7","private":false,"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"alexvyber","email":"alexvyber@gmail.com"},"_npmVersion":"11.11.0","description":"Public SDK for building trusted Brand Map admin extensions.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@brand-map/ts-client":"0.0.10-alpha.4"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.3","@types/react":"^19.2.10"},"peerDependencies":{"react":">=19"},"_npmOperationalInternal":{"tmp":"tmp/admin-extension-sdk_0.0.10-alpha.4_1778529064437_0.5090764413540732","host":"s3://npm-registry-packages-npm-production"}},"0.0.10-alpha.5":{"name":"@brand-map/admin-extension-sdk","version":"0.0.10-alpha.5","_id":"@brand-map/admin-extension-sdk@0.0.10-alpha.5","maintainers":[{"name":"alexvyber","email":"alexvyber@gmail.com"}],"dist":{"shasum":"a7e2ac3e5bdcdc104d0526f0bdd2f86b1f1e264c","tarball":"https://registry.npmjs.org/@brand-map/admin-extension-sdk/-/admin-extension-sdk-0.0.10-alpha.5.tgz","fileCount":6,"integrity":"sha512-t1CiPk/NYKv3sWoaGrnwzKhbXFhwh8omgQAHz17N1hI9fctJDuMmFnyI7InNfnPOXlJkp/6UJo6oh+Re+I1Rhw==","signatures":[{"sig":"MEUCIGAa/7tO29KqJgrRcr8W+yH+YBaisOZL5S66WKJ7Y3D9AiEA79VyBsAFrTZCRq4PR3gUmBgMYDFXL+GabjR3o6h3xvQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":24250},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"6e23178105bce90071ebdbb7a9a74511d3f94ac7","private":false,"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"alexvyber","email":"alexvyber@gmail.com"},"_npmVersion":"11.11.0","description":"Public SDK for building trusted Brand Map admin extensions.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@brand-map/ts-client":"0.0.10-alpha.5"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.3","@types/react":"^19.2.10"},"peerDependencies":{"react":">=19"},"_npmOperationalInternal":{"tmp":"tmp/admin-extension-sdk_0.0.10-alpha.5_1778529952284_0.4416360564201578","host":"s3://npm-registry-packages-npm-production"}},"0.0.10-alpha.7":{"name":"@brand-map/admin-extension-sdk","version":"0.0.10-alpha.7","_id":"@brand-map/admin-extension-sdk@0.0.10-alpha.7","maintainers":[{"name":"alexvyber","email":"alexvyber@gmail.com"}],"dist":{"shasum":"7716dc3d562ac31729f030cd03be9e22a5be7180","tarball":"https://registry.npmjs.org/@brand-map/admin-extension-sdk/-/admin-extension-sdk-0.0.10-alpha.7.tgz","fileCount":6,"integrity":"sha512-Qo46/6jt18W8/MXJAcEWAkPgfeGO/8gKAV9IOhlpagOjSEPc60QGPkBGL9wRpSV8EPTivq2gGg+Qp7Z9ba1YXg==","signatures":[{"sig":"MEUCIB+PO38f5wIrJVZyJXWIXUKNvj7e2JmljIGY4LEjJuY7AiEA4qwB+CW1m55TjM29Nn0oeuiaF1slLfxlziMOPv8j7IE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":39968},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"3d890b1169b926bb6340ff57888ee6d3764e7793","private":false,"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"alexvyber","email":"alexvyber@gmail.com"},"_npmVersion":"11.11.0","description":"Public SDK for building trusted Brand Map admin extensions.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@brand-map/ts-client":"0.0.10-alpha.7"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.3","@types/react":"^19.2.10"},"peerDependencies":{"react":">=19"},"_npmOperationalInternal":{"tmp":"tmp/admin-extension-sdk_0.0.10-alpha.7_1778580098869_0.7377979843283982","host":"s3://npm-registry-packages-npm-production"}},"0.0.10-alpha.8":{"name":"@brand-map/admin-extension-sdk","version":"0.0.10-alpha.8","_id":"@brand-map/admin-extension-sdk@0.0.10-alpha.8","maintainers":[{"name":"alexvyber","email":"alexvyber@gmail.com"}],"dist":{"shasum":"2aeb9d40ea1dd19aead3017d3ceaf29facd1711a","tarball":"https://registry.npmjs.org/@brand-map/admin-extension-sdk/-/admin-extension-sdk-0.0.10-alpha.8.tgz","fileCount":6,"integrity":"sha512-GR0bUpla54TmtKtSNIWo+PEK5gRwuN1qhL/LbxwN4AADdOEosjnayffRLSdgZZqEquP1pjLMkpSzjBkbPkebCQ==","signatures":[{"sig":"MEYCIQCqvFA/ede36GWoM76m1u6eQgoPJDpo/E21UyRWkvtAPQIhANzmhj/SAqSRz0VHQowB3iCUtkFBhRslGCVbMeLmv9Ji","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":39968},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"75a4e042a2b9e6ae8b649c3fb314f18ad8a075cc","private":false,"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"alexvyber","email":"alexvyber@gmail.com"},"_npmVersion":"11.11.0","description":"Public SDK for building trusted Brand Map admin extensions.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@brand-map/ts-client":"0.0.10-alpha.8"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.3","@types/react":"^19.2.10"},"peerDependencies":{"react":">=19"},"_npmOperationalInternal":{"tmp":"tmp/admin-extension-sdk_0.0.10-alpha.8_1778588792271_0.08517059096990476","host":"s3://npm-registry-packages-npm-production"}},"0.0.10-alpha.27":{"name":"@brand-map/admin-extension-sdk","version":"0.0.10-alpha.27","_id":"@brand-map/admin-extension-sdk@0.0.10-alpha.27","maintainers":[{"name":"alexvyber","email":"alexvyber@gmail.com"}],"dist":{"shasum":"1422e8a39a8ea4e566355736608a0ccee857148a","tarball":"https://registry.npmjs.org/@brand-map/admin-extension-sdk/-/admin-extension-sdk-0.0.10-alpha.27.tgz","fileCount":2,"integrity":"sha512-aznYVQSuJUeqUcnf01UYDtV7e8OXngHPqESRhKSh7Fh02XZhkvS7HRUdyoVZ2/CdJw8k2S/QqZO6UGgAo+yD8Q==","signatures":[{"sig":"MEUCIQCi94YpRCq3PW4S9ulMp2LktThvSEMfN6I80z7z9b9KmAIgacqkc7uorlpCUMZGZlyGNFCkdBqwucrjlbnDD/eZjNY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":16807},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"alexvyber","email":"alexvyber@gmail.com"},"_npmVersion":"11.11.0","description":"Public SDK for building trusted Brand Map admin extensions.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@brand-map/ts-client":"0.0.10-alpha.27"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"catalog:dev","@types/react":"^19.2.10","@brand-map/typescript-config":"workspace:*"},"peerDependencies":{"react":">=19"},"_npmOperationalInternal":{"tmp":"tmp/admin-extension-sdk_0.0.10-alpha.27_1779964595268_0.8167401106870618","host":"s3://npm-registry-packages-npm-production"}},"0.0.10-alpha.28":{"name":"@brand-map/admin-extension-sdk","version":"0.0.10-alpha.28","_id":"@brand-map/admin-extension-sdk@0.0.10-alpha.28","maintainers":[{"name":"alexvyber","email":"alexvyber@gmail.com"}],"dist":{"shasum":"8d8c54af56eddd029296267ad249476866f49c36","tarball":"https://registry.npmjs.org/@brand-map/admin-extension-sdk/-/admin-extension-sdk-0.0.10-alpha.28.tgz","fileCount":2,"integrity":"sha512-meJyrg+e2rX3G0Lt/m0SYHZjsmN/QUKtvdNIvfsDRYQh63czhs/RvJF9Lj/4ppCgqotjca7A239hkv7foYPbRw==","signatures":[{"sig":"MEYCIQD+DPcIhIgArUZfrLVyDjLuMD8z0OV6sMfbmLZ/H0X6EgIhAL8ctFajP/gmVK36y2IzUv3W5EzYgoafVDoY2WLqQGY5","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":16807},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"alexvyber","email":"alexvyber@gmail.com"},"_npmVersion":"11.11.0","description":"Public SDK for building trusted Brand Map admin extensions.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@brand-map/ts-client":"0.0.10-alpha.28"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"catalog:dev","@types/react":"^19.2.10","@brand-map/typescript-config":"workspace:*"},"peerDependencies":{"react":">=19"},"_npmOperationalInternal":{"tmp":"tmp/admin-extension-sdk_0.0.10-alpha.28_1779967126353_0.14522046294634938","host":"s3://npm-registry-packages-npm-production"}},"0.0.10-alpha.29":{"name":"@brand-map/admin-extension-sdk","version":"0.0.10-alpha.29","_id":"@brand-map/admin-extension-sdk@0.0.10-alpha.29","maintainers":[{"name":"alexvyber","email":"alexvyber@gmail.com"}],"dist":{"shasum":"f631dde2a44b446c5a8fb65aab11d060e85ed844","tarball":"https://registry.npmjs.org/@brand-map/admin-extension-sdk/-/admin-extension-sdk-0.0.10-alpha.29.tgz","fileCount":2,"integrity":"sha512-PnoaGbc2qVuJ07hOaWk8XBLalvOVvPmKZ4vKDcdLlVS3iTIxjElZxyxM9yxeX5Nyx6qGtRwC3FZ+D8HfblGptg==","signatures":[{"sig":"MEUCIQC+ZkrrMukzIJgoZd7dKqGjQzlNPa/RVQP8cN+CLQ8HBAIgP68hNRWRxsItvzJulz4wsckr+C1Zx+8sYQFg8o0NP1E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":16807},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"alexvyber","email":"alexvyber@gmail.com"},"_npmVersion":"11.11.0","description":"Public SDK for building trusted Brand Map admin extensions.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@brand-map/ts-client":"0.0.10-alpha.29"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"catalog:dev","@types/react":"^19.2.10","@brand-map/typescript-config":"workspace:*"},"peerDependencies":{"react":">=19"},"_npmOperationalInternal":{"tmp":"tmp/admin-extension-sdk_0.0.10-alpha.29_1779971186912_0.8999658127941119","host":"s3://npm-registry-packages-npm-production"}},"0.0.10-alpha.30":{"name":"@brand-map/admin-extension-sdk","version":"0.0.10-alpha.30","_id":"@brand-map/admin-extension-sdk@0.0.10-alpha.30","maintainers":[{"name":"alexvyber","email":"alexvyber@gmail.com"}],"dist":{"shasum":"1f92976032d324001685558b3bb9d1edba130e75","tarball":"https://registry.npmjs.org/@brand-map/admin-extension-sdk/-/admin-extension-sdk-0.0.10-alpha.30.tgz","fileCount":8,"integrity":"sha512-sdjhjUAO8TdRRAVhqJ+kjNp8M93J4SYojNtnb1C0L1HDHr3xi1GUGYlFwCv34BWEhoiGcersMfEr9PabxWNJIA==","signatures":[{"sig":"MEUCIQDepbE3lKjepkKqnY5bVSo1Alwn8kPYybpukRQ5GpyqTQIgKIoS7VHJKaqRaP2Qyy5GrarZuwRuXD0/2C1qINDa1Zc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":33440},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"alexvyber","email":"alexvyber@gmail.com"},"_npmVersion":"11.11.0","description":"Public SDK for building trusted Brand Map admin extensions.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@brand-map/ts-client":"0.0.10-alpha.30"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"catalog:dev","@types/react":"^19.2.10","@brand-map/typescript-config":"workspace:*"},"peerDependencies":{"react":">=19"},"_npmOperationalInternal":{"tmp":"tmp/admin-extension-sdk_0.0.10-alpha.30_1779971633056_0.6900684738369809","host":"s3://npm-registry-packages-npm-production"}},"0.0.10-alpha.31":{"name":"@brand-map/admin-extension-sdk","version":"0.0.10-alpha.31","_id":"@brand-map/admin-extension-sdk@0.0.10-alpha.31","maintainers":[{"name":"alexvyber","email":"alexvyber@gmail.com"}],"dist":{"shasum":"b3c28ed36e6ca08a3f37f47b96a0d107e7490d07","tarball":"https://registry.npmjs.org/@brand-map/admin-extension-sdk/-/admin-extension-sdk-0.0.10-alpha.31.tgz","fileCount":8,"integrity":"sha512-kFrS7CQfaiqCXeSWioyBAPUnnmFImVxUkcjt11oJRZ65l5OdFxthf+DJnhGgYt7Yowj1TlMxSbiSKdj5pOOAiQ==","signatures":[{"sig":"MEUCIE48um0mlprpCF1qop96ccEoCN3DaVQeF4H62jinWnt4AiEAqs91JADvojhnGAnCSDoCst1BEHgaQz0AA19bVzvSW74=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":33440},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"alexvyber","email":"alexvyber@gmail.com"},"_npmVersion":"11.11.0","description":"Public SDK for building trusted Brand Map admin extensions.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@brand-map/ts-client":"0.0.10-alpha.31"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"catalog:dev","@types/react":"^19.2.10","@brand-map/typescript-config":"workspace:*"},"peerDependencies":{"react":">=19"},"_npmOperationalInternal":{"tmp":"tmp/admin-extension-sdk_0.0.10-alpha.31_1779976703463_0.49327971750234045","host":"s3://npm-registry-packages-npm-production"}},"0.0.10-alpha.33":{"name":"@brand-map/admin-extension-sdk","version":"0.0.10-alpha.33","_id":"@brand-map/admin-extension-sdk@0.0.10-alpha.33","maintainers":[{"name":"alexvyber","email":"alexvyber@gmail.com"}],"dist":{"shasum":"12c6c52f6e7ceb0a9cd671c4f10846c37386d8c0","tarball":"https://registry.npmjs.org/@brand-map/admin-extension-sdk/-/admin-extension-sdk-0.0.10-alpha.33.tgz","fileCount":8,"integrity":"sha512-yNl7nl21BXGTwwPDyjQsDae6VJztlwS6Z4iNl1Kj6NbD/5PtzOOSvkvN4jw1blvQgkYR+XDtZoczRJQd8dy2hw==","signatures":[{"sig":"MEUCIBP2YjX3fpVr1bXNypYKy8mho4t5PLgm7UJWnbidi2lfAiEAp0AuTbHPAeAYZhcujxqPGlvC9WOfj7XQ5peQ6PbMNZ8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":33440},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"alexvyber","email":"alexvyber@gmail.com"},"_npmVersion":"11.11.0","description":"Public SDK for building trusted Brand Map admin extensions.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@brand-map/ts-client":"0.0.10-alpha.33"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"catalog:dev","@types/react":"^19.2.10","@brand-map/typescript-config":"workspace:*"},"peerDependencies":{"react":">=19"},"_npmOperationalInternal":{"tmp":"tmp/admin-extension-sdk_0.0.10-alpha.33_1779987068787_0.9688567877453285","host":"s3://npm-registry-packages-npm-production"}},"0.0.10-alpha.34":{"name":"@brand-map/admin-extension-sdk","version":"0.0.10-alpha.34","description":"Public SDK for building trusted Brand Map admin extensions.","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit -p tsconfig.json"},"dependencies":{"@brand-map/ts-client":"0.0.10-alpha.34"},"devDependencies":{"@brand-map/typescript-config":"workspace:*","@types/react":"^19.2.10","typescript":"catalog:dev"},"peerDependencies":{"react":">=19"},"_id":"@brand-map/admin-extension-sdk@0.0.10-alpha.34","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-0jfekrWZ3yXTm0eMRAKG9kFn5e26qZNM/d5yWPydC4INLKKLJ1vIUAjnv4Cg+yzn3+bIacF96p9gBTw9Vv+CwA==","shasum":"31284b1849d0a81e09e58eb85f214a740ec082c8","tarball":"https://registry.npmjs.org/@brand-map/admin-extension-sdk/-/admin-extension-sdk-0.0.10-alpha.34.tgz","fileCount":8,"unpackedSize":33440,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDDQfYU6SNh55zXQrwlNudZwEl/ovrmbVJZ8/t4NNL13AIgWqEBnRXb06d8XPxnbkynLVNs0JOJXoo0ikuz/w+c3Ms="}]},"_npmUser":{"name":"alexvyber","email":"alexvyber@gmail.com"},"directories":{},"maintainers":[{"name":"alexvyber","email":"alexvyber@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/admin-extension-sdk_0.0.10-alpha.34_1780073085154_0.9606371113754759"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-11T19:51:04.304Z","modified":"2026-05-29T16:44:45.422Z","0.0.10-alpha.4":"2026-05-11T19:51:04.577Z","0.0.10-alpha.5":"2026-05-11T20:05:52.435Z","0.0.10-alpha.7":"2026-05-12T10:01:39.027Z","0.0.10-alpha.8":"2026-05-12T12:26:32.423Z","0.0.10-alpha.27":"2026-05-28T10:36:35.426Z","0.0.10-alpha.28":"2026-05-28T11:18:46.649Z","0.0.10-alpha.29":"2026-05-28T12:26:27.064Z","0.0.10-alpha.30":"2026-05-28T12:33:53.191Z","0.0.10-alpha.31":"2026-05-28T13:58:23.593Z","0.0.10-alpha.33":"2026-05-28T16:51:08.967Z","0.0.10-alpha.34":"2026-05-29T16:44:45.287Z"},"description":"Public SDK for building trusted Brand Map admin extensions.","maintainers":[{"name":"alexvyber","email":"alexvyber@gmail.com"}],"readme":"# Brand Map Admin Extension SDK\n\n`@brand-map/admin-extension-sdk` is the public SDK for building Brand Map admin extensions.\n\nThe current model is React-based and ESM-first. An extension exports a manifest object plus lazy route, widget, and slot components. The admin host loads that manifest, validates it, mounts its pages under `/extensions/:extensionId/...`, and injects a small host API through React context.\n\nThis package is for extension authors.\n\nHost-side catalog, installation, and marketplace APIs now live in `@brand-map/admin-extension-management`.\n\n## What an extension can do\n\nToday an admin extension can:\n\n- Register one or more admin pages.\n- Register widgets that open over any admin page.\n- Add UI into supported extension slots.\n- Declare admin permissions and check them at runtime.\n- Call the admin backend through the host session.\n- Use host utilities such as navigation and toasts.\n- Render docs anchors for host-owned guided docs.\n\nThe first supported slot is:\n\n- `product.list.headerActions`\n\nSupported admin permissions are:\n\n- `products:read`\n- `products:write`\n- `brands:read`\n- `brands:write`\n- `users:read`\n- `users:write`\n- `settings:read`\n- `settings:write`\n\n## Installation\n\n```bash\nnpm install @brand-map/admin-extension-sdk react\n```\n\nThe SDK expects the extension to run inside the admin host's React tree. In published extensions, keep React aligned with the host and avoid bundling a second React runtime.\n\n## Quick start\n\nCreate an entry module that exports a manifest with `defineAdminExtension`:\n\n```tsx\nimport { defineAdminExtension } from \"@brand-map/admin-extension-sdk\"\n\nexport default defineAdminExtension({\n  id: \"acme.product-reviews\",\n  name: \"Product Reviews\",\n  version: \"1.0.0\",\n  description: \"Review tooling inside Brand Map admin.\",\n  permissions: [\"products:read\"],\n  nav: [\n    {\n      label: \"Reviews\",\n      path: \"reviews\",\n      icon: \"star\",\n      order: 100,\n    },\n  ],\n  routes: [\n    {\n      path: \"reviews\",\n      component: () => import(\"./pages/reviews-page\"),\n    },\n  ],\n  widgets: [\n    {\n      id: \"review-assistant\",\n      label: \"Review Assistant\",\n      icon: \"message-circle\",\n      order: 10,\n      component: () => import(\"./widgets/review-assistant\"),\n    },\n  ],\n  slots: [\n    {\n      slot: \"product.list.headerActions\",\n      component: () => import(\"./slots/product-list-header-action\"),\n      order: 10,\n    },\n  ],\n})\n```\n\nThen implement route, widget, or slot components with `useAdmin()`:\n\n```tsx\nimport { useAdmin } from \"@brand-map/admin-extension-sdk\"\n\nexport default function ReviewsPage() {\n  const admin = useAdmin()\n\n  async function handleSync() {\n    const reviews = await admin.request<{ total: number }>(\"/reviews/stats\")\n    admin.toast.success(`Loaded ${reviews.total} reviews.`)\n  }\n\n  return <button onClick={handleSync}>Signed in as {admin.currentUser?.email ?? \"unknown user\"}</button>\n}\n```\n\n## Manifest reference\n\nThe extension manifest is the contract between your package and the admin host.\n\n```ts\ntype AdminExtension = {\n  id: string\n  name: string\n  version: string\n  description?: string\n  permissions?: readonly AdminPermission[]\n  nav?: readonly AdminExtensionNavItem[]\n  routes?: readonly AdminExtensionRoute[]\n  slots?: readonly AdminSlotContribution[]\n  widgets?: readonly AdminExtensionWidget[]\n}\n```\n\n### `id`\n\n`id` is the stable runtime identifier for the extension.\n\nRules enforced by the host:\n\n- Must be a non-empty string.\n- Must match `^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$`.\n- Must match the identifier used by the host registry entry.\n- Must be unique across loaded extensions.\n\nGood examples:\n\n- `acme.product-reviews`\n- `notificator`\n\nRejected examples:\n\n- `Acme.ProductReviews`\n- `acme_reviews`\n- `.acme`\n\n### `name`\n\nHuman-readable extension name shown by the host.\n\n### `version`\n\nHuman-readable version string. The host only validates that it is a non-empty string.\n\n### `description`\n\nOptional descriptive text for catalog or host UI.\n\n### `permissions`\n\nPermissions requested by the extension. The host validates both of these conditions:\n\n- Every requested permission must be known.\n- Every requested permission must also be granted by the host installation record.\n\nIf either check fails, the extension is rejected and does not render.\n\n### `nav`\n\nNavigation items the host can surface for the extension.\n\n```ts\ntype AdminExtensionNavItem = {\n  label: string\n  path: string\n  icon?: string\n  order?: number\n}\n```\n\nNotes:\n\n- `path` must be a relative extension path such as `reviews` or `reports/monthly`.\n- The first route segment `docs` is reserved by the host for extension Markdown docs.\n- Lower `order` values sort first.\n- Missing `order` is treated as `0`.\n- The host builds the final URL as `/extensions/{extensionId}/{path}`.\n\n### `routes`\n\nRoute registrations for extension pages.\n\n```ts\ntype AdminExtensionRoute = {\n  path: string\n  component: AdminExtensionComponentLoader<AdminExtensionRouteComponentProps>\n}\n```\n\nNotes:\n\n- Components must be lazy loaders that resolve to a default React component export.\n- Routes are sorted by normalized path in the host.\n- A route path may be nested, for example `reports/monthly`.\n\n### `widgets`\n\nWidget registrations let an extension expose UI that opens over the current admin page from the right-side widget rail.\n\n```ts\ntype AdminExtensionWidget = {\n  id: string\n  label: string\n  icon?: string\n  order?: number\n  component: AdminExtensionComponentLoader<AdminExtensionWidgetComponentProps>\n}\n```\n\nNotes:\n\n- `id` must be unique inside the extension and use lower-case letters, numbers, dots, or dashes.\n- `label` is shown by the host for the widget launcher.\n- Components must be lazy loaders that resolve to a default React component export.\n- Lower `order` values render first.\n- Widget components use `useAdmin()` for the same host API as routes and slots, and can call `useAdminWidget()` for widget-specific state such as `widgetId` and `close()`.\n\n### `slots`\n\nSlot contributions let an extension inject UI into specific host locations.\n\n```ts\ntype AdminSlotContribution<TSlotId extends AdminSlotId = AdminSlotId> = {\n  slot: TSlotId\n  component: AdminExtensionComponentLoader<AdminSlotPropsById[TSlotId]>\n  order?: number\n}\n```\n\nNotes:\n\n- Components must be lazy loaders.\n- Lower `order` values render first.\n- Unknown slots are rejected during manifest validation.\n\n## Route paths\n\nRoute and nav paths are relative to the extension root.\n\nValid:\n\n- `overview`\n- `reports/monthly`\n- `settings/general`\n\nRejected by the host:\n\n- `/overview`\n- `./overview`\n- `../overview`\n- `docs`\n- `docs/setup`\n- `https://example.com`\n- `overview?tab=one`\n- `overview#section`\n\nThe host normalizes leading and trailing slashes when matching routes, but you should still declare clean relative paths in the manifest.\n\n## Route mounting\n\nExtension routes are mounted under:\n\n```txt\n/{storeId}/extensions/{extensionId}\n/{storeId}/extensions/{extensionId}/{path}\n```\n\nExamples:\n\n- `id: \"acme.product-reviews\"` and `path: \"reviews\"` becomes `/extensions/acme.product-reviews/reviews`\n- `id: \"acme.product-reviews\"` and `path: \"reports/monthly\"` becomes `/extensions/acme.product-reviews/reports/monthly`\n\nIf a user navigates to an extension id that is not loaded, or to a path that the extension did not register, the host shows an extension status page instead of rendering the component.\n\n## Writing route components\n\nRoute components are ordinary React components with access to the host API through `useAdmin()`.\n\nThe host currently passes these route props:\n\n```ts\ntype AdminExtensionRouteComponentProps = {\n  extensionId: string\n  path: string\n  splat: string\n  storeId?: string\n}\n```\n\nExample:\n\n```tsx\nimport type { AdminExtensionRouteComponentProps } from \"@brand-map/admin-extension-sdk\"\nimport { useAdmin } from \"@brand-map/admin-extension-sdk\"\n\nexport default function ReportsPage({ extensionId, path }: AdminExtensionRouteComponentProps) {\n  const admin = useAdmin()\n\n  return (\n    <section>\n      <h1>{extensionId}</h1>\n      <p>Current extension path: {path}</p>\n      <button onClick={() => admin.navigate(\"/products\")}>Back to products</button>\n    </section>\n  )\n}\n```\n\n## Writing slot components\n\nThe currently supported slot prop shape is:\n\n```ts\ntype ProductListHeaderActionsSlotProps = {\n  extensionId: string\n  storeId?: string\n}\n```\n\nExample:\n\n```tsx\nimport { useAdmin } from \"@brand-map/admin-extension-sdk\"\n\nexport default function ProductListHeaderAction() {\n  const admin = useAdmin()\n\n  return <button onClick={() => admin.toast.success(\"Rendered from extension\")}>Run action</button>\n}\n```\n\nSlot components render inside the host UI, so keep them compact and resilient.\n\n## Host API\n\nUse `useAdmin()` inside route and slot components:\n\n```ts\nfunction useAdmin(): AdminHostApi\n```\n\nIf it is called outside an admin extension host, it throws:\n\n```txt\nuseAdmin must be used inside an admin extension host.\n```\n\nThe host API shape is:\n\n```ts\ntype AdminHostApi = {\n  currentUser: {\n    id: string\n    name?: string | null\n    email?: string | null\n    image?: string | null\n  } | null\n  extensionId: string\n  hasPermission: (permission: AdminPermission) => boolean\n  storeId?: string\n  navigate: (to: string) => void\n  toast: {\n    success: (message: string) => void\n    info: (message: string) => void\n    warning: (message: string) => void\n    error: (message: string) => void\n  }\n  request: <TResponse = unknown>(input: string | URL | Request, init?: RequestInit) => Promise<TResponse>\n}\n```\n\n### `currentUser`\n\nInformation about the currently signed-in admin user, if available.\n\n### `extensionId`\n\nStable id of the hosted admin extension instance.\n\n### `hasPermission(permission)`\n\nChecks whether the permission was granted to this extension instance.\n\nThere is also a convenience hook:\n\n```ts\nuseAdminPermission(permission: AdminPermission): boolean\n```\n\nExample:\n\n```tsx\nimport { useAdminPermission } from \"@brand-map/admin-extension-sdk\"\n\nexport default function ReviewsGate() {\n  const canWrite = useAdminPermission(\"products:write\")\n\n  return canWrite ? <button>Edit review settings</button> : null\n}\n```\n\n## Docs anchors\n\nUse `AdminDocsAnchor` to mark interface locations that package-root Markdown docs can target with `brand-map://anchor/<anchorId>?route=<route>`.\n\n```tsx\nimport { AdminDocsAnchor } from \"@brand-map/admin-extension-sdk\"\n\nexport function SettingsSection() {\n  return (\n    <section>\n      <AdminDocsAnchor id=\"settings-form\" />\n      <h2>Settings</h2>\n    </section>\n  )\n}\n```\n\nAnchor ids must contain only letters, numbers, dots, colons, underscores, or hyphens, and are scoped to the extension id by the host.\n\n### `navigate(to)`\n\nNavigates using the admin host router. Use it for internal app navigation.\n\n### `toast`\n\nUser notifications surfaced by the host.\n\nExample:\n\n```tsx\nadmin.toast.info(\"Sync started.\")\nadmin.toast.success(\"Sync completed.\")\nadmin.toast.error(\"Sync failed.\")\n```\n\n### `request(input, init)`\n\nPerforms an authenticated request through the admin host.\n\nCurrent host behavior:\n\n- Attaches the current session bearer token.\n- Only allows requests to the configured admin backend API origin and path.\n- Throws an `Error` when the response is not `2xx`.\n- Returns `undefined` for `204 No Content`.\n- Returns parsed JSON for JSON responses.\n- Returns plain text for non-JSON responses.\n\nThat makes it the preferred way for extensions to talk to the Brand Map backend.\n\nExample:\n\n```tsx\ntype ProductSummary = {\n  id: string\n  title: string\n}\n\nasync function loadProducts(admin: ReturnType<typeof useAdmin>) {\n  return admin.request<ProductSummary[]>(\"/products\")\n}\n```\n\n## Host integration\n\nIf you are building the admin host, use `@brand-map/admin-extension-management` for catalog and installation operations, and keep the host runtime private to the admin app.\n\nThe admin host still needs an internal registry entry that looks like this:\n\n```ts\ntype InstalledAdminExtension = {\n  extensionName?: string\n  id: string\n  enabled?: boolean\n  grantedPermissions: readonly AdminPermission[]\n  load: () => Promise<{ default: AdminExtension }>\n}\n```\n\nExample:\n\n```ts\nexport const installedAdminExtensions = [\n  {\n    id: \"acme.product-reviews\",\n    enabled: true,\n    grantedPermissions: [\"products:read\"],\n    load: () => import(\"@acme/product-reviews-admin-extension\"),\n  },\n]\n```\n\nThe host runtime validates the loaded manifest before exposing it to users. Invalid manifests are reported as load errors and are not mounted.\n\n## Host-side runtime behavior\n\nThe current admin host does the following:\n\n- Skips registry entries where `enabled === false`.\n- Rejects duplicate extension ids.\n- Sorts loaded extensions by `manifest.name`.\n- Sorts nav items and slot contributions by `order`.\n- Sorts routes by normalized path.\n- Wraps lazy extension components in `React.Suspense`.\n- Wraps extension rendering in an error boundary.\n\nIf a route or slot component throws during render, the host shows an \"Extension failed to render\" alert instead of crashing the admin app.\n\nFor catalog, installation, configuration metadata, and secret placeholders, use `@brand-map/admin-extension-management`.\n\n## Validation and failure cases\n\nThe current admin host rejects an extension when any of these are true:\n\n- The module default export is not an object.\n- `id`, `name`, or `version` is missing or empty.\n- The exported `id` does not match the registry entry id.\n- The `id` format is invalid.\n- `permissions`, `nav`, `routes`, or `slots` is not an array.\n- A requested permission is unknown.\n- A requested permission was not granted.\n- A nav path or route path is not a relative route path.\n- A route component loader is not a function.\n- A slot id is unknown.\n- A slot component loader is not a function.\n\nThe host also reports duplicate ids across registry entries.\n\n## Recommended package structure\n\nOne workable structure is:\n\n```txt\nsrc/\n  index.ts\n  pages/\n    reviews-page.tsx\n    reports-page.tsx\n  slots/\n    product-list-header-action.tsx\n```\n\nWhere:\n\n- `src/index.ts` exports the extension manifest.\n- `pages/*` export default React components for routes.\n- `slots/*` export default React components for host slots.\n\n## Practical guidance\n\n- Keep the manifest small and declarative. Put real UI code behind lazy imports.\n- Use permission checks in the UI even when the host already validates grants.\n- Treat `admin.request()` as the single backend entry point from extension components.\n- Keep route and nav paths stable once users can bookmark them.\n- Keep slot UI minimal so it feels native inside host pages.\n- Fail gracefully. The host isolates render errors, but users still see the error message.\n\n## Minimal end-to-end example\n\n```tsx\n// src/index.ts\nimport { defineAdminExtension } from \"@brand-map/admin-extension-sdk\"\n\nexport default defineAdminExtension({\n  id: \"acme.inventory\",\n  name: \"Inventory\",\n  version: \"1.0.0\",\n  permissions: [\"products:read\"],\n  nav: [{ label: \"Inventory\", path: \"inventory\" }],\n  routes: [\n    {\n      path: \"inventory\",\n      component: () => import(\"./pages/inventory-page\"),\n    },\n  ],\n})\n```\n\n```tsx\n// src/pages/inventory-page.tsx\nimport { useAdmin } from \"@brand-map/admin-extension-sdk\"\n\ntype InventoryRow = {\n  id: string\n  sku: string\n}\n\nexport default function InventoryPage() {\n  const admin = useAdmin()\n\n  async function handleLoad() {\n    const rows = await admin.request<InventoryRow[]>(\"/inventory\")\n    admin.toast.success(`Loaded ${rows.length} inventory rows.`)\n  }\n\n  return (\n    <div>\n      <h1>Inventory</h1>\n      <p>{admin.currentUser?.email ?? \"Unknown user\"}</p>\n      <button onClick={handleLoad}>Load inventory</button>\n    </div>\n  )\n}\n```\n\n## Related source in this repo\n\n- SDK entry point: `admin/packages/admin-extension-sdk/src/index.ts`\n- Host runtime validation: `admin/modules/dashboard-extension/src/runtime.ts`\n- Host provider/runtime registry: `admin/modules/dashboard-extension/src/providers/extension-provider.tsx`\n- Host backend request adapter: `admin/modules/dashboard-extension/src/request-backend.ts`\n","readmeFilename":"README.md"}