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

@kleroai/shopify

v0.2.0

Published

Klero feedback board for Shopify embedded apps using Polaris web components

Readme

@kleroai/shopify

Embed Klero feedback, roadmap, and changelog inside your Shopify embedded app. Renders using Shopify Polaris web components so the UI feels native to the Shopify admin.

Requirements

  • A Klero project (get your slug from the Klero dashboard)

  • A Shopify embedded app that loads Polaris web components:

    <script src="https://cdn.shopify.com/shopifycloud/polaris.js"></script>
  • React >= 18

Installation

npm install @kleroai/shopify

Quick Start

1. Add environment variables

KLERO_SLUG=your-project-slug
KLERO_JWT_SECRET=your-jwt-secret   # from Klero dashboard → Settings → JWT Secret
KLERO_BASE_URL=                    # optional: custom domain

2. Add a Feedback route

// app/routes/app.feedback.tsx
import type { LoaderFunctionArgs } from 'react-router';
import { useLoaderData } from 'react-router';
import { KleroFeedbackPage, signKleroJwtFromShopifySession } from '@kleroai/shopify';
import { authenticate } from '../shopify.server';

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { session } = await authenticate.admin(request);

  return {
    slug: process.env.KLERO_SLUG || '',
    baseUrl: process.env.KLERO_BASE_URL || undefined,
    customerToken: await signKleroJwtFromShopifySession(
      process.env.KLERO_JWT_SECRET || '',
      session
    ),
  };
};

export default function FeedbackPage() {
  const { slug, baseUrl, customerToken } = useLoaderData<typeof loader>();
  return <KleroFeedbackPage slug={slug} baseUrl={baseUrl} customerToken={customerToken ?? undefined} />;
}

Repeat the same pattern for Roadmap (KleroRoadmapPage) and Changelog (KleroChangelogPage).

Page Components

These are the recommended way to add Klero to your app. They handle the <s-page> wrapper, heading updates, and header button injection automatically.

KleroFeedbackPage

<KleroFeedbackPage slug="my-project" customerToken={token} />

| Prop | Type | Required | Description | |------|------|----------|-------------| | slug | string | Yes | Your Klero project slug | | baseUrl | string | No | Override API base URL (for custom domains) | | customerToken | string | No | JWT for the logged-in Shopify user |

KleroRoadmapPage

<KleroRoadmapPage slug="my-project" customerToken={token} />

| Prop | Type | Required | Description | |------|------|----------|-------------| | slug | string | Yes | Your Klero project slug | | baseUrl | string | No | Override API base URL | | customerToken | string | No | JWT for the logged-in user | | roadmapSlug | string | No | Show a specific roadmap by slug |

KleroChangelogPage

<KleroChangelogPage slug="my-project" customerToken={token} />

| Prop | Type | Required | Description | |------|------|----------|-------------| | slug | string | Yes | Your Klero project slug | | baseUrl | string | No | Override API base URL | | customerToken | string | No | JWT for the logged-in user |

Low-level Widget Components

Use these if you need to render widgets inside your own <s-page> and manage heading/view state yourself.

KleroFeedbackWidget

<KleroFeedbackWidget
  slug="my-project"
  customerToken={token}
  pageEl={pageEl}
  onDetailChange={(title) => setHeading(title ?? 'Feedback')}
/>

| Prop | Type | Required | Description | |------|------|----------|-------------| | slug | string | Yes | Your Klero project slug | | pageEl | HTMLElement | No | The <s-page> DOM element — used to inject action buttons into the page header | | baseUrl | string | No | Override API base URL (for custom domains) | | customerToken | string | No | JWT for the logged-in Shopify user | | onDetailChange | (title: string \| null) => void | No | Called when drilling into/out of a feedback item (use to update page heading) | | onViewChange | (view: 'list' \| 'detail') => void | No | Called on view transitions |

KleroRoadmapWidget

<KleroRoadmapWidget
  slug="my-project"
  customerToken={token}
  pageEl={pageEl}
  onDetailChange={(title) => setHeading(title ?? 'Roadmap')}
/>

| Prop | Type | Required | Description | |------|------|----------|-------------| | slug | string | Yes | Your Klero project slug | | pageEl | HTMLElement | No | The <s-page> DOM element | | roadmapSlug | string | No | Show a specific roadmap by slug | | baseUrl | string | No | Override API base URL | | customerToken | string | No | JWT for the logged-in user | | onDetailChange | (title: string \| null) => void | No | Called when entering/leaving item detail | | onViewChange | (view: 'list' \| 'detail' \| 'create') => void | No | Called on view transitions |

KleroChangelogWidget

<KleroChangelogWidget
  slug="my-project"
  customerToken={token}
  pageEl={pageEl}
  onDetailChange={(title) => setHeading(title ?? 'Changelog')}
/>

| Prop | Type | Required | Description | |------|------|----------|-------------| | slug | string | Yes | Your Klero project slug | | pageEl | HTMLElement | No | The <s-page> DOM element | | baseUrl | string | No | Override API base URL | | customerToken | string | No | JWT for the logged-in user | | onDetailChange | (title: string \| null) => void | No | Called when entering/leaving an entry |

Server-side authentication

Use signKleroJwtFromShopifySession in a loader to issue a customer JWT from the Shopify admin session. This maps the logged-in merchant/staff user to a Klero customer so their votes and comments are attributed correctly.

// In a React Router loader
import { signKleroJwtFromShopifySession } from '@kleroai/shopify';

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { session } = await authenticate.admin(request);

  return {
    customerToken: await signKleroJwtFromShopifySession(
      process.env.KLERO_JWT_SECRET || '',
      session
    ),
  };
};

Returns null if the session has no associated user (e.g. offline token). The component accepts null / undefined and falls back to anonymous mode.

Important: signKleroJwtFromShopifySession requires an online Shopify session to read the associated user. If your shopify.server.ts was generated from the default Shopify CLI template it uses offline tokens by default, and users will appear anonymous. Add useOnlineTokens: true to your shopifyApp() config:

// app/shopify.server.ts
const shopify = shopifyApp({
  // ...
  useOnlineTokens: true,
});

After adding this, re-authenticate your app (reinstall or go through the OAuth flow again) so Shopify creates online sessions.

For lower-level control, use signKleroJwt directly:

import { signKleroJwt } from '@kleroai/shopify';

const token = await signKleroJwt(process.env.KLERO_JWT_SECRET, {
  id: 'user-123',
  email: '[email protected]',
  name: 'Jane Smith',
});

Imperative API

For non-React usage, mount widgets imperatively:

import { KleroFeedback, KleroRoadmap, KleroChangelog } from '@kleroai/shopify';

const feedback = await KleroFeedback.init({
  slug: 'my-project',
  el: '#feedback-container',
  customerToken: token,
});

// Clean up
feedback.destroy();

Note: init() is async — it loads the Klero UI bundle from the server on first call, then resolves with the widget instance.

License

Proprietary.