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

@palettelab/sdk

v0.1.16

Published

Palette Platform SDK for building plugins and apps

Readme

@palettelab/sdk

Frontend SDK for building Palette plugins and internal apps.

Use this package from plugin frontends created with @palettelab/cli. It provides typed platform context, authenticated API helpers, React hooks for common Palette resources, iframe sandbox bridge helpers, install configuration helpers, and test utilities.

Install

npm install @palettelab/sdk react react-dom

react and react-dom are peer dependencies. Plugin bundles should externalize React and @palettelab/sdk; the Palette CLI handles this for standard templates.

Recommended Developer Flow

Create apps with the Palette CLI and use this SDK from the generated frontend.

npx --yes @palettelab/cli@latest init simple-todo --template database
cd simple-todo
npm install
npx --yes @palettelab/cli@latest dev

pltt dev starts the local SDK simulator. It provides mock platform context, authenticated API helpers, local backend routing, and a local database when the app declares database support. This is the fastest loop for building UI, calling backend routes, and checking SDK behavior.

For real Palette OS testing without Docker, configure a hosted sandbox and run:

npx --yes @palettelab/cli@latest login \
  --env staging \
  --url https://YOUR-PALETTE-STAGING-URL \
  --token pltt_xxxxx

npx --yes @palettelab/cli@latest dev --sandbox --env staging

The hosted sandbox publishes a preview into the configured Palette environment and returns an OS preview URL. Use that URL when the app must be tested with real login context, organization context, Data Room APIs, storage, install state, review/publish state, logs, permissions, and platform APIs.

Staging URL And Token

The staging URL is the base URL of the Palette backend/API environment used by the CLI. It must serve backend API paths such as:

/api/v1/health
/api/v1/appstore/sign-upload
/api/v1/appstore/publish
/api/v1/developer/publish-tokens

Validate it before configuring the CLI:

curl https://YOUR-PALETTE-STAGING-URL/api/v1/health

If this returns backend health JSON, the URL is correct. If it returns the frontend app HTML, use the backend domain instead or configure your reverse proxy so /api/v1/* and /api/superadmin/* are routed to the backend.

Publish tokens start with pltt_. Developers can create one after logging in:

  1. Open Palette OS.
  2. Open Settings.
  3. Open Developer.
  4. Create a developer publish token.
  5. Copy it immediately. The raw token is shown only once.

Then save it locally:

npx --yes @palettelab/cli@latest login \
  --env staging \
  --url https://YOUR-PALETTE-STAGING-URL \
  --token pltt_xxxxx

The CLI stores this in ~/.palette/config.json with restricted file permissions. You can also use environment variables:

export PALETTE_STAGING_URL=https://YOUR-PALETTE-STAGING-URL
export PALETTE_STAGING_TOKEN=pltt_xxxxx

Local Simulator Versus Hosted OS

Use the local simulator for everyday development:

npx --yes @palettelab/cli@latest dev

Use hosted sandbox for full OS behavior:

npx --yes @palettelab/cli@latest dev --sandbox --env staging

For internal sandbox environments where manual approval should not block app testing, run the Palette backend with:

APPSTORE_AUTO_APPROVE_SANDBOX_PREVIEWS=true

When enabled, passing sandbox preview publishes become active automatically, so the developer can immediately test the app inside the OS preview URL.

Exports

import {
  PluginProvider,
  usePlatform,
  createPaletteClient,
  DataRoomClient,
  StorageClient,
  usePluginTranslations,
  translate,
  normalizePaletteLanguage,
  usePluginTasks,
  usePluginDataRooms,
  usePluginChat,
  apiFetch,
  apiUpload,
  setBaseUrl,
  getBaseUrl,
  createSandboxBridge,
  isSandboxRuntime,
  getInstallConfig,
  updateInstallConfig,
  hasPermission,
  hasAnyPermission,
  hasAllPermissions,
  PaletteApiError,
  errorFromResponse,
  isPaletteApiError,
  createMockPlatformContext,
  withPluginProvider,
} from "@palettelab/sdk"

Subpath exports are also available:

import { usePlatform } from "@palettelab/sdk/hooks"
import type { PluginManifest } from "@palettelab/sdk/types"
import { PluginProvider } from "@palettelab/sdk/components"

Helper Reference

Public frontend helpers exported by @palettelab/sdk:

  • Provider/context: PluginProvider, usePlatform, PlatformCtx.
  • Client facade: createPaletteClient(platform?).
  • API: apiFetch(path, init?), apiUpload(path, file, fieldName?, extraFields?), setBaseUrl(url), getBaseUrl().
  • Errors: PaletteApiError, errorFromResponse(response), isPaletteApiError(error).
  • Data Rooms: DataRoomClient, dataRooms, plus list, create, get, folder, ensureRoom, requireRoomByName, findRoomByName, createFolder, ensureFolder, findFolderByName, resolveFolderPath, findFileByName, requestUpload, confirmUpload, and uploadFile.
  • Storage: StorageClient, upload(file, options), resume(file, options), and uploadToSignedUrl(uploadUrl, file, contentType?).
  • Install config: getInstallConfig(pluginId), updateInstallConfig(pluginId, values).
  • Organization/user: UserClient, OrganizationClient, including current, updateProfile, listMine, listMembers, getMember, getMemberByEmail, inviteMember, and updateMemberRole.
  • Permissions: hasPermission(ctx, permission), hasAnyPermission(ctx, permissions), hasAllPermissions(ctx, permissions).
  • Translations: normalizePaletteLanguage, translate, usePluginTranslations.
  • Hooks: usePluginTasks, usePluginDataRooms, usePluginChat.
  • Sandbox: createSandboxBridge, isSandboxRuntime.
  • Testing: createMockPlatformContext, withPluginProvider.
  • Types: PluginManifest, PluginComponentProps, PlatformContext, PaletteClient, resource, task, chat, data-room, user, organization, translation, and sandbox bridge types.

Plugin Root

The platform passes plugin runtime context into your root component. Wrap your UI with PluginProvider so hooks can read it.

import { PluginProvider, usePlatform, type PluginComponentProps } from "@palettelab/sdk"

function App() {
  const { user, organizationId, pluginId } = usePlatform()

  return (
    <main>
      <h1>{pluginId}</h1>
      <p>{user?.email}</p>
      <p>Organization: {organizationId}</p>
    </main>
  )
}

export default function PluginRoot(props: PluginComponentProps) {
  return (
    <PluginProvider value={props}>
      <App />
    </PluginProvider>
  )
}

App Translations And OS Language

Palette OS passes the current global language into every plugin through usePlatform(). Keep translations in your app repo, then let the SDK choose the right language.

import { usePluginTranslations, type TranslationResources } from "@palettelab/sdk"

const resources = {
  en: { title: "Invoices", greeting: "Hello, {{name}}" },
  ko: { title: "청구서", greeting: "안녕하세요, {{name}}님" },
} satisfies TranslationResources

function App() {
  const { t, language, setLanguage } = usePluginTranslations(resources)

  return (
    <main>
      <h1>{t("title")}</h1>
      <button onClick={() => setLanguage(language === "ko" ? "en" : "ko")}>
        {language === "ko" ? "EN" : "KO"}
      </button>
    </main>
  )
}

The same context is available in pltt dev, so local development and OS runtime use the same translation path.

Next-Compatible Apps

The Palette appstore runtime loads plugin frontends as native React modules. If an app wants a Next-style config file, set frontend.framework in palette-plugin.json and place the config at frontend/next.config.ts.

{
  "frontend": {
    "entry": "./frontend/src/index.tsx",
    "sandbox": true,
    "framework": "next",
    "config": "./frontend/next.config.ts"
  }
}

The CLI reads that config in pltt dev, pltt test, pltt package, and pltt publish. In native mode it supports env values from Next config, NEXT_PUBLIC_* environment variables, and path aliases from frontend/tsconfig.json.

Palette App Router

For OS-native routed apps, set frontend.framework to palette-app and point frontend.entry at frontend/app. The CLI scans app-directory UI files and still publishes one safe browser bundle.

{
  "frontend": {
    "entry": "./frontend/app",
    "sandbox": true,
    "framework": "palette-app"
  }
}

Supported UI files are layout.tsx, page.tsx, loading.tsx, error.tsx, and not-found.tsx. Static routes, route groups, [id], [...slug], and [[...slug]] are supported. Use Link, useRouter, usePathname, useSearchParams, and useParams from @palettelab/sdk, or use the next/link and next/navigation compatibility imports in palette-app projects.

Server-side Next features are intentionally not part of this mode. Put APIs, database access, permissions, and secrets in the plugin backend.

Palette Client

Use createPaletteClient() when an app needs common Palette OS services without remembering raw API routes.

import { createPaletteClient, usePlatform } from "@palettelab/sdk"

function App() {
  const platform = usePlatform()
  const palette = createPaletteClient(platform)

  async function upload(file: File) {
    const rooms = await palette.dataRooms.list()
    await palette.dataRooms.uploadFile(rooms[0].id, file)
    palette.toast.success("Uploaded")
  }

  return <button onClick={() => palette.user.current()}>Load profile</button>
}

Included clients:

  • palette.user.current() and palette.user.updateProfile()
  • palette.organization.currentId(), currentRole(), listMine(), listMembers(), getMember(), getMemberByEmail(), inviteMember(), and updateMemberRole()
  • palette.dataRooms.list(), create(), get(), folder(), createFolder(), ensureFolder(), findRoomByName(), findFolderByName(), resolveFolderPath(), findFileByName(), and uploadFile()
  • palette.config.get() and palette.config.update(values), with optional plugin ID override
  • palette.permissions.has(), hasAny(), and hasAll()
  • palette.storage.upload(file, options), resume(file, options), and uploadToSignedUrl()
  • palette.toast.success(), error(), and info()

These helpers are intentionally thin wrappers over platform APIs. Apps can still use apiFetch() directly for custom backend routes.

App storage uploads are scoped by Palette to:

uploads/apps/{app_name}_{plugin_id}/{organisation_slug}_{organisation_id}/{file}

Declare "storage" in platform_services, then upload directly from the browser with resumable chunking and progress:

const palette = createPaletteClient(usePlatform())

await palette.storage.upload(file, {
  key: `receipts/${file.name}`,
  onProgress: (p) => setPercent(p.percentage),
})

Member helpers operate on the active organisation. Use members:read for listing or looking up members, and members:write for invitations or role updates. When the runtime provides declared app permissions, these helpers check those permissions before calling the platform API. Member deletion/removal is intentionally not exposed through the app SDK.

Data Room Folders By Name

Apps can create or reuse custom Data Room folders by name:

const palette = createPaletteClient(usePlatform())

const room = await palette.dataRooms.ensureRoom("Finance")
const invoices = await palette.dataRooms.ensureFolder(room.id, "Invoices")

await palette.dataRooms.uploadFile(room.id, file, {
  folderId: invoices.id,
})

Apps can also resolve nested folders:

const folder = await palette.dataRooms.resolveFolderPath(
  room.id,
  "Clients/Acme/Invoices",
  { create: true },
)

To read existing files by name:

const room = await palette.dataRooms.requireRoomByName("Finance")
const folder = await palette.dataRooms.resolveFolderPath(room.id, "Clients/Acme/Invoices")
const file = folder
  ? await palette.dataRooms.findFileByName(room.id, "jan.pdf", { folderId: folder.id })
  : null

Permissions

Use permission helpers to keep UI actions aligned with backend permission gates.

const palette = createPaletteClient(usePlatform())
const canWrite = palette.permissions.has("resources:write")

Backend routes should still enforce permissions with the Python SDK. Frontend permission checks are for UX only.

API Helpers

Use apiFetch for authenticated platform API calls. It includes credentials and performs the platform refresh flow for normal portal runtime.

import { apiFetch } from "@palettelab/sdk"

const res = await apiFetch("/api/v1/tasks")
const tasks = await res.json()

Use apiUpload for multipart uploads.

import { apiUpload } from "@palettelab/sdk"

await apiUpload("/api/v1/resources/upload", file)

Resource Hooks

The SDK includes hooks for common Palette resources:

  • usePluginTasks
  • usePluginDataRooms
  • usePluginChat
import { usePluginTasks } from "@palettelab/sdk"

export function TaskList() {
  const { tasks, loading, error, refetch } = usePluginTasks()

  if (loading) return <p>Loading...</p>
  if (error) return <p>{error}</p>

  return (
    <ul>
      {tasks.map((task) => (
        <li key={task.id}>{task.title}</li>
      ))}
    </ul>
  )
}

Install Configuration

Plugins can read and update their installation configuration through helper APIs.

import { getInstallConfig, updateInstallConfig } from "@palettelab/sdk"

const config = await getInstallConfig("my-plugin")
await updateInstallConfig("my-plugin", { ...config, enabled: true })

Sandbox Bridge

Appstore plugin frontends run inside an iframe sandbox by default. Use createSandboxBridge when you need to explicitly call host-proxied APIs from sandbox runtime code.

import { createSandboxBridge, isSandboxRuntime } from "@palettelab/sdk"

if (isSandboxRuntime()) {
  const bridge = createSandboxBridge()
  const result = await bridge.apiFetch("/api/v1/tasks")
  console.log(result)
}

For standard React plugin templates, the runtime wiring is already handled by the platform and CLI.

Testing

Use the SDK test utilities to render plugin components with a mock platform context.

import { createMockPlatformContext, withPluginProvider } from "@palettelab/sdk"

const ctx = createMockPlatformContext({
  pluginId: "my-plugin",
  organizationId: 1,
})

const Wrapped = withPluginProvider(MyPluginComponent, ctx)

Package Contents

The npm package ships compiled CommonJS, ESM, and TypeScript declarations from dist/.

Related Packages

  • @palettelab/cli provides the pltt command for scaffolding, local dev, validation, packaging, and publishing.
  • palette-sdk is the Python backend SDK for FastAPI plugin routes, permissions, tools, DB helpers, RLS, and test helpers.