@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/shopifyQuick 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 domain2. 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:
signKleroJwtFromShopifySessionrequires an online Shopify session to read the associated user. If yourshopify.server.tswas generated from the default Shopify CLI template it uses offline tokens by default, and users will appear anonymous. AdduseOnlineTokens: trueto yourshopifyApp()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.
