npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@brand-map/admin-extension-sdk

v0.0.10-alpha.34

Published

Public SDK for building trusted Brand Map admin extensions.

Downloads

2,586

Readme

Brand Map Admin Extension SDK

@brand-map/admin-extension-sdk is the public SDK for building Brand Map admin extensions.

The 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.

This package is for extension authors.

Host-side catalog, installation, and marketplace APIs now live in @brand-map/admin-extension-management.

What an extension can do

Today an admin extension can:

  • Register one or more admin pages.
  • Register widgets that open over any admin page.
  • Add UI into supported extension slots.
  • Declare admin permissions and check them at runtime.
  • Call the admin backend through the host session.
  • Use host utilities such as navigation and toasts.
  • Render docs anchors for host-owned guided docs.

The first supported slot is:

  • product.list.headerActions

Supported admin permissions are:

  • products:read
  • products:write
  • brands:read
  • brands:write
  • users:read
  • users:write
  • settings:read
  • settings:write

Installation

npm install @brand-map/admin-extension-sdk react

The 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.

Quick start

Create an entry module that exports a manifest with defineAdminExtension:

import { defineAdminExtension } from "@brand-map/admin-extension-sdk"

export default defineAdminExtension({
  id: "acme.product-reviews",
  name: "Product Reviews",
  version: "1.0.0",
  description: "Review tooling inside Brand Map admin.",
  permissions: ["products:read"],
  nav: [
    {
      label: "Reviews",
      path: "reviews",
      icon: "star",
      order: 100,
    },
  ],
  routes: [
    {
      path: "reviews",
      component: () => import("./pages/reviews-page"),
    },
  ],
  widgets: [
    {
      id: "review-assistant",
      label: "Review Assistant",
      icon: "message-circle",
      order: 10,
      component: () => import("./widgets/review-assistant"),
    },
  ],
  slots: [
    {
      slot: "product.list.headerActions",
      component: () => import("./slots/product-list-header-action"),
      order: 10,
    },
  ],
})

Then implement route, widget, or slot components with useAdmin():

import { useAdmin } from "@brand-map/admin-extension-sdk"

export default function ReviewsPage() {
  const admin = useAdmin()

  async function handleSync() {
    const reviews = await admin.request<{ total: number }>("/reviews/stats")
    admin.toast.success(`Loaded ${reviews.total} reviews.`)
  }

  return <button onClick={handleSync}>Signed in as {admin.currentUser?.email ?? "unknown user"}</button>
}

Manifest reference

The extension manifest is the contract between your package and the admin host.

type AdminExtension = {
  id: string
  name: string
  version: string
  description?: string
  permissions?: readonly AdminPermission[]
  nav?: readonly AdminExtensionNavItem[]
  routes?: readonly AdminExtensionRoute[]
  slots?: readonly AdminSlotContribution[]
  widgets?: readonly AdminExtensionWidget[]
}

id

id is the stable runtime identifier for the extension.

Rules enforced by the host:

  • Must be a non-empty string.
  • Must match ^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$.
  • Must match the identifier used by the host registry entry.
  • Must be unique across loaded extensions.

Good examples:

  • acme.product-reviews
  • notificator

Rejected examples:

  • Acme.ProductReviews
  • acme_reviews
  • .acme

name

Human-readable extension name shown by the host.

version

Human-readable version string. The host only validates that it is a non-empty string.

description

Optional descriptive text for catalog or host UI.

permissions

Permissions requested by the extension. The host validates both of these conditions:

  • Every requested permission must be known.
  • Every requested permission must also be granted by the host installation record.

If either check fails, the extension is rejected and does not render.

nav

Navigation items the host can surface for the extension.

type AdminExtensionNavItem = {
  label: string
  path: string
  icon?: string
  order?: number
}

Notes:

  • path must be a relative extension path such as reviews or reports/monthly.
  • The first route segment docs is reserved by the host for extension Markdown docs.
  • Lower order values sort first.
  • Missing order is treated as 0.
  • The host builds the final URL as /extensions/{extensionId}/{path}.

routes

Route registrations for extension pages.

type AdminExtensionRoute = {
  path: string
  component: AdminExtensionComponentLoader<AdminExtensionRouteComponentProps>
}

Notes:

  • Components must be lazy loaders that resolve to a default React component export.
  • Routes are sorted by normalized path in the host.
  • A route path may be nested, for example reports/monthly.

widgets

Widget registrations let an extension expose UI that opens over the current admin page from the right-side widget rail.

type AdminExtensionWidget = {
  id: string
  label: string
  icon?: string
  order?: number
  component: AdminExtensionComponentLoader<AdminExtensionWidgetComponentProps>
}

Notes:

  • id must be unique inside the extension and use lower-case letters, numbers, dots, or dashes.
  • label is shown by the host for the widget launcher.
  • Components must be lazy loaders that resolve to a default React component export.
  • Lower order values render first.
  • 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().

slots

Slot contributions let an extension inject UI into specific host locations.

type AdminSlotContribution<TSlotId extends AdminSlotId = AdminSlotId> = {
  slot: TSlotId
  component: AdminExtensionComponentLoader<AdminSlotPropsById[TSlotId]>
  order?: number
}

Notes:

  • Components must be lazy loaders.
  • Lower order values render first.
  • Unknown slots are rejected during manifest validation.

Route paths

Route and nav paths are relative to the extension root.

Valid:

  • overview
  • reports/monthly
  • settings/general

Rejected by the host:

  • /overview
  • ./overview
  • ../overview
  • docs
  • docs/setup
  • https://example.com
  • overview?tab=one
  • overview#section

The host normalizes leading and trailing slashes when matching routes, but you should still declare clean relative paths in the manifest.

Route mounting

Extension routes are mounted under:

/{storeId}/extensions/{extensionId}
/{storeId}/extensions/{extensionId}/{path}

Examples:

  • id: "acme.product-reviews" and path: "reviews" becomes /extensions/acme.product-reviews/reviews
  • id: "acme.product-reviews" and path: "reports/monthly" becomes /extensions/acme.product-reviews/reports/monthly

If 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.

Writing route components

Route components are ordinary React components with access to the host API through useAdmin().

The host currently passes these route props:

type AdminExtensionRouteComponentProps = {
  extensionId: string
  path: string
  splat: string
  storeId?: string
}

Example:

import type { AdminExtensionRouteComponentProps } from "@brand-map/admin-extension-sdk"
import { useAdmin } from "@brand-map/admin-extension-sdk"

export default function ReportsPage({ extensionId, path }: AdminExtensionRouteComponentProps) {
  const admin = useAdmin()

  return (
    <section>
      <h1>{extensionId}</h1>
      <p>Current extension path: {path}</p>
      <button onClick={() => admin.navigate("/products")}>Back to products</button>
    </section>
  )
}

Writing slot components

The currently supported slot prop shape is:

type ProductListHeaderActionsSlotProps = {
  extensionId: string
  storeId?: string
}

Example:

import { useAdmin } from "@brand-map/admin-extension-sdk"

export default function ProductListHeaderAction() {
  const admin = useAdmin()

  return <button onClick={() => admin.toast.success("Rendered from extension")}>Run action</button>
}

Slot components render inside the host UI, so keep them compact and resilient.

Host API

Use useAdmin() inside route and slot components:

function useAdmin(): AdminHostApi

If it is called outside an admin extension host, it throws:

useAdmin must be used inside an admin extension host.

The host API shape is:

type AdminHostApi = {
  currentUser: {
    id: string
    name?: string | null
    email?: string | null
    image?: string | null
  } | null
  extensionId: string
  hasPermission: (permission: AdminPermission) => boolean
  storeId?: string
  navigate: (to: string) => void
  toast: {
    success: (message: string) => void
    info: (message: string) => void
    warning: (message: string) => void
    error: (message: string) => void
  }
  request: <TResponse = unknown>(input: string | URL | Request, init?: RequestInit) => Promise<TResponse>
}

currentUser

Information about the currently signed-in admin user, if available.

extensionId

Stable id of the hosted admin extension instance.

hasPermission(permission)

Checks whether the permission was granted to this extension instance.

There is also a convenience hook:

useAdminPermission(permission: AdminPermission): boolean

Example:

import { useAdminPermission } from "@brand-map/admin-extension-sdk"

export default function ReviewsGate() {
  const canWrite = useAdminPermission("products:write")

  return canWrite ? <button>Edit review settings</button> : null
}

Docs anchors

Use AdminDocsAnchor to mark interface locations that package-root Markdown docs can target with brand-map://anchor/<anchorId>?route=<route>.

import { AdminDocsAnchor } from "@brand-map/admin-extension-sdk"

export function SettingsSection() {
  return (
    <section>
      <AdminDocsAnchor id="settings-form" />
      <h2>Settings</h2>
    </section>
  )
}

Anchor ids must contain only letters, numbers, dots, colons, underscores, or hyphens, and are scoped to the extension id by the host.

navigate(to)

Navigates using the admin host router. Use it for internal app navigation.

toast

User notifications surfaced by the host.

Example:

admin.toast.info("Sync started.")
admin.toast.success("Sync completed.")
admin.toast.error("Sync failed.")

request(input, init)

Performs an authenticated request through the admin host.

Current host behavior:

  • Attaches the current session bearer token.
  • Only allows requests to the configured admin backend API origin and path.
  • Throws an Error when the response is not 2xx.
  • Returns undefined for 204 No Content.
  • Returns parsed JSON for JSON responses.
  • Returns plain text for non-JSON responses.

That makes it the preferred way for extensions to talk to the Brand Map backend.

Example:

type ProductSummary = {
  id: string
  title: string
}

async function loadProducts(admin: ReturnType<typeof useAdmin>) {
  return admin.request<ProductSummary[]>("/products")
}

Host integration

If 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.

The admin host still needs an internal registry entry that looks like this:

type InstalledAdminExtension = {
  extensionName?: string
  id: string
  enabled?: boolean
  grantedPermissions: readonly AdminPermission[]
  load: () => Promise<{ default: AdminExtension }>
}

Example:

export const installedAdminExtensions = [
  {
    id: "acme.product-reviews",
    enabled: true,
    grantedPermissions: ["products:read"],
    load: () => import("@acme/product-reviews-admin-extension"),
  },
]

The host runtime validates the loaded manifest before exposing it to users. Invalid manifests are reported as load errors and are not mounted.

Host-side runtime behavior

The current admin host does the following:

  • Skips registry entries where enabled === false.
  • Rejects duplicate extension ids.
  • Sorts loaded extensions by manifest.name.
  • Sorts nav items and slot contributions by order.
  • Sorts routes by normalized path.
  • Wraps lazy extension components in React.Suspense.
  • Wraps extension rendering in an error boundary.

If a route or slot component throws during render, the host shows an "Extension failed to render" alert instead of crashing the admin app.

For catalog, installation, configuration metadata, and secret placeholders, use @brand-map/admin-extension-management.

Validation and failure cases

The current admin host rejects an extension when any of these are true:

  • The module default export is not an object.
  • id, name, or version is missing or empty.
  • The exported id does not match the registry entry id.
  • The id format is invalid.
  • permissions, nav, routes, or slots is not an array.
  • A requested permission is unknown.
  • A requested permission was not granted.
  • A nav path or route path is not a relative route path.
  • A route component loader is not a function.
  • A slot id is unknown.
  • A slot component loader is not a function.

The host also reports duplicate ids across registry entries.

Recommended package structure

One workable structure is:

src/
  index.ts
  pages/
    reviews-page.tsx
    reports-page.tsx
  slots/
    product-list-header-action.tsx

Where:

  • src/index.ts exports the extension manifest.
  • pages/* export default React components for routes.
  • slots/* export default React components for host slots.

Practical guidance

  • Keep the manifest small and declarative. Put real UI code behind lazy imports.
  • Use permission checks in the UI even when the host already validates grants.
  • Treat admin.request() as the single backend entry point from extension components.
  • Keep route and nav paths stable once users can bookmark them.
  • Keep slot UI minimal so it feels native inside host pages.
  • Fail gracefully. The host isolates render errors, but users still see the error message.

Minimal end-to-end example

// src/index.ts
import { defineAdminExtension } from "@brand-map/admin-extension-sdk"

export default defineAdminExtension({
  id: "acme.inventory",
  name: "Inventory",
  version: "1.0.0",
  permissions: ["products:read"],
  nav: [{ label: "Inventory", path: "inventory" }],
  routes: [
    {
      path: "inventory",
      component: () => import("./pages/inventory-page"),
    },
  ],
})
// src/pages/inventory-page.tsx
import { useAdmin } from "@brand-map/admin-extension-sdk"

type InventoryRow = {
  id: string
  sku: string
}

export default function InventoryPage() {
  const admin = useAdmin()

  async function handleLoad() {
    const rows = await admin.request<InventoryRow[]>("/inventory")
    admin.toast.success(`Loaded ${rows.length} inventory rows.`)
  }

  return (
    <div>
      <h1>Inventory</h1>
      <p>{admin.currentUser?.email ?? "Unknown user"}</p>
      <button onClick={handleLoad}>Load inventory</button>
    </div>
  )
}

Related source in this repo

  • SDK entry point: admin/packages/admin-extension-sdk/src/index.ts
  • Host runtime validation: admin/modules/dashboard-extension/src/runtime.ts
  • Host provider/runtime registry: admin/modules/dashboard-extension/src/providers/extension-provider.tsx
  • Host backend request adapter: admin/modules/dashboard-extension/src/request-backend.ts