Kanorio Elements
The same protected players, PDF viewer, lightbox, curriculum and customer login that power hosted Kanorio sites, packaged for sites you build yourself. Signed URLs, resume, progress reporting and future upgrades (HLS, watermarks, captions) come for free.
Install
Install @kanorio/sdk and @kanorio/elements from npm, or load the CDN bundle with a single script tag. Every SDK helper is a thin wrapper over standard HTTP endpoints—pure fetch / curl works everywhere without the SDK.
npm install @kanorio/sdk @kanorio/elements npx @kanorio/cli types --site https://s-xxx.kanorio.com --out src/kanorio.d.ts npx @kanorio/cli doctor --site https://s-xxx.kanorio.com
<link rel="stylesheet" href="https://cdn.kanorio.com/elements/v1/elements.css"> <script type="module" src="https://cdn.kanorio.com/elements/v1/kanorio-elements.js"></script> <kanorio-analytics site-url="https://s-xxx.kanorio.com"></kanorio-analytics> <kanorio-login site-url="https://s-xxx.kanorio.com" site-name="Demo"></kanorio-login> <kanorio-curriculum site-url="https://s-xxx.kanorio.com" product="react-course"></kanorio-curriculum> <kanorio-unit site-url="https://s-xxx.kanorio.com" deliverable="react-course"></kanorio-unit>
import { KanorioProvider, KanorioAnalytics, KanorioLogin, KanorioUnit } from "@kanorio/elements";
import { createDeliveryClient, createRevalidateHandler, seoRewrites } from "@kanorio/sdk";
import "@kanorio/elements/elements.css";
// 1. next.config.ts: rewrite /sitemap.xml, /robots.txt, /llms.txt, /feed.xml -> Kanorio Delivery API
// rewrites: async () => seoRewrites({ siteUrl: process.env.KANORIO_SITE_URL!, framework: "next" })
// 2. app/api/revalidate/route.ts: verify the X-Kanorio-Signature (t=…,v1=…) webhook header & call revalidateTag(cacheTags)
// export const POST = createRevalidateHandler({ signingSecret: process.env.KANORIO_WEBHOOK_SECRET!, revalidateTag });
// 3. Pure REST fallback (no SDK required):
// curl https://s-xxx.kanorio.com/api/v1/collections/blog/items?expand=authors,categories
// curl https://s-xxx.kanorio.com/api/v1/seo/jsonld?path=/about
// 4. <KanorioAnalytics /> reports every client-side navigation (pushState / replaceState / popstate) automatically.
// Add your storefront origin to Settings → Developer → Allowed origins, or the browser will block the beacon.
<KanorioProvider siteUrl="https://s-xxx.kanorio.com" locale="en" theme="auto">
<KanorioAnalytics />
<KanorioLogin siteName="Demo" />
<KanorioUnit deliverable="react-course" onProgress={(e) => console.log(e)} />
</KanorioProvider>Elements
<kanorio-login>— magic-link / 6-digit code sign-in. Stores the customer Bearer inlocalStorage["kanorio.customer.token"]and fireskanorio:login.<kanorio-unit deliverable="…">— the protected player: outline sidebar, video / audio / PDF / text / file / embed units, locked and not-yet-released states, automatic progress reporting. Fireskanorio:unit-changeandkanorio:progress.<kanorio-curriculum product="…">— public outline for a product (chapters, units, durations, free previews playable anonymously). No login required.<kanorio-media src="…" mime-type="…">— public media player for a URL.<kanorio-lightbox images='["…"]'>— image lightbox with zoom, keyboard and download / share controls governed by the site's media protection.<kanorio-form form="…">— interactive form with client validation, anti-spam challenge token, private file upload tickets, and submission handling.<kanorio-analytics>— privacy-first pageview and SPA route change tracker (pushState / replaceState / popstate) using non-blocking sendBeacon.
Common attributes: site-url (defaults to the current origin), locale (defaults to <html lang>; 58 locales built in), theme (dark / light / auto), token. Styling uses --media-*, --site-* and --brand-color CSS variables with sensible defaults. Vidstack and pdf.js load on demand.
Capabilities with drop-in Elements
Full matrix| Capability | Elements | API + SDK | Hosted |
|---|---|---|---|
| Customer login (magic link / 6-digit code) | <kanorio-login> (stores Bearer in localStorage, kanorio:login event) | Customer POST /auth/challenge, POST /auth/verify → Bearer; GET /me; DELETE /session | /account/login |
| Entitlements (who may access what) | <kanorio-unit> shows locked / preview / not-released states | Agent entitlements.grant / revoke / list / check; Customer GET /access/:id | Backoffice contact → Access tab; /data/entitlements; /account/content |
| Forms & lead capture | <KanorioForm /> (<kanorio-form>), <KanorioBotProtection />, useKanorioBotProtection() | Agent forms.list/get, submissions.list/get; Delivery GET /forms/:id, GET /forms/:id/challenge, POST /forms/:id/upload-request, POST /forms/:id/submit; SDK forms.client({ siteUrl }) | /marketing/forms, /data/form_submissions; page form section |
| Deliverables (courses, downloads) | <kanorio-curriculum> (public outline, free previews) | Agent deliverables.*, deliverable_units.*; Delivery GET /products/:slug outline; Customer GET /deliverables, GET /deliverables/:key | /store/deliverables, /data/deliverables, /data/deliverable_units; product content-curriculum section |
| Protected playback & downloads | <kanorio-unit> (video, audio, PDF, text, files, embeds) | Customer POST /units/:id/open (600 s signed URL); Delivery POST /units/:id/open for previews | /account/content/%3Akey |
| Progress & completion | <kanorio-unit> reports progress automatically; kanorio:progress event | Customer POST /progress, GET /deliverables/:key/progress; Agent progress.list | /data/progress; /account/content progress bars |
| Site team RBAC (custom roles, access requests), Agent PAT / OAuth 2.1 & Customer Session Bearer | <kanorio-login> | PAT (knr_pat_), OAuth 2.1 (knr_at_) with site scope; auth.whoami?websiteId= reports role vs. token scope; Customer Bearer (knr_cat_) bound to (websiteId, contactId) | /settings/team, /settings/team/roles, /account/api-access |
| Public media, responsive images, LQIP placeholders & lightbox | <kanorio-media src mime-type>, <kanorio-lightbox images='[…]'> (reads the site's media protection when protection is omitted) | Agent media.* (images, files, protected deliverable files, media.update focus & alt text); Delivery assets map (url, width, height, alt, focus, watermarked, placeholder 16px WebP data URL); GET /api/v1/site mediaProtection | /media library; gallery & media sections |
| First-party visitor & AI crawler traffic analytics (`/analytics`) | <KanorioAnalytics /> | Delivery POST /analytics/pageview (anonymous, per-IP rate-limited, records path / referrer / UA category); Agent analytics.* | /analytics |
Versioning
Elements and the SDK share a major version and follow the API stability policy (opens in a new tab): attributes and props are only added, never changed or removed within a major. Pin the CDN URL to a major (/elements/v1/) and add Subresource Integrity when you self-host.
@kanorio/sdk Subpath Modules (Preview, Forms, Edge & Revalidate)
@kanorio/sdk is runtime-agnostic (Node, Cloudflare Workers, Vercel Edge, Deno, Bun, and browsers).// 1. Next.js Draft Mode & Astro preview route handler (@kanorio/sdk/preview)
// app/api/draft/route.ts — verifies knr_pv_ token format & signature against Delivery API,
// blocks open redirects, sets HttpOnly "kanorio_preview" cookie, and 307 redirects.
import { draftMode } from "next/headers";
import { createPreviewRouteHandler, tokenFromCookieHeader, previewHeaders } from "@kanorio/sdk/preview";
export const GET = createPreviewRouteHandler({
siteUrl: process.env.NEXT_PUBLIC_KANORIO_SITE_URL,
enableDraftMode: async () => (await draftMode()).enable(),
});
// In a Server Component when reading drafts:
// const token = tokenFromCookieHeader( requestHeaders.get("cookie") ) ?? process.env.KANORIO_PREVIEW_TOKEN;
// fetch(url, { headers: previewHeaders(token!), cache: "no-store" });
// 2. Custom headless form UI helpers (@kanorio/sdk/forms)
import { createFormsHelpers } from "@kanorio/sdk/forms";
const formsApi = createFormsHelpers({ siteUrl: "https://s-xxx.kanorio.com" });
const schema = await formsApi.get("form_contact");
const challenge = await formsApi.challenge("form_contact");
// Optional private file upload ticket: await formsApi.requestUpload("form_contact", { fieldId, fileName, mimeType, fileSize });
const result = await formsApi.submit("form_contact", {
answers: { name: "Alex", email: "alex@example.com" },
protection: challenge,
consent: "checked",
});
// 3. Edge crawler firewall, 301/302 redirects & bot-hit telemetry (@kanorio/sdk/edge)
// proxy.ts (Next.js 16; on Next.js 15 name it middleware.ts and export `middleware`)
// Syncs crawlerRules.blockedTokens and /api/v1/redirects every 60s. Redirects match exact paths only.
import type { NextFetchEvent, NextRequest } from "next/server";
import { createKanorioMiddleware } from "@kanorio/sdk/edge";
const kanorioEdge = createKanorioMiddleware({
siteUrl: process.env.NEXT_PUBLIC_KANORIO_SITE_URL!,
});
export function proxy(request: NextRequest, event: NextFetchEvent) {
return kanorioEdge.handleRequest(request, event);
}
// Skip build assets so crawler fetches of JS/CSS are not reported as bot hits
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};@kanorio/cli Command Reference
kanorio login --token knr_pat_... or KANORIO_TOKEN.# Types & External Storefront Health (no PAT required for public types) npx @kanorio/cli types --site https://s-xxx.kanorio.com --out src/kanorio.d.ts npx @kanorio/cli doctor --site <siteId> [--switching] # Auth & Sites npx @kanorio/cli login --token knr_pat_... npx @kanorio/cli sites list | sites get <siteId> npx @kanorio/cli sites create --name "Sunny Bakery" --locale en [--brief brief.json] npx @kanorio/cli jobs get <jobId> # Theme Settings, Globals (Header / Footer) & Pages npx @kanorio/cli settings get <siteId> | settings update <siteId> --brand-color "#2563eb" npx @kanorio/cli globals get <siteId> | globals replace-header <siteId> --file header.json npx @kanorio/cli pages list <siteId> | pages get <siteId> <pageId> npx @kanorio/cli pages create <siteId> --title "About" --slug /about npx @kanorio/cli pages replace-sections <siteId> <pageId> --file sections.json npx @kanorio/cli sections describe [type...] [--detail full] # Publish, Custom Domains & Media npx @kanorio/cli publish <siteId> [--subdomain sunny-bakery] | unpublish <siteId> npx @kanorio/cli domains list <siteId> | domains status <siteId> | domains bind <siteId> example.com npx @kanorio/cli media list <siteId> | media upload <siteId> --file ./hero.jpg